/* sqzass 테마 — marigold
 *
 * 이 사이트가 쓰는 테마이자 sqzass 로 만든 첫 테마다. 이름은 이 시트의 유일한
 * 색에서 따왔다. 두 번째 테마가 생기면 그것도 자기 색으로 불리면 된다.
 *
 * 차가운 니어블랙 위의 가운데 정렬 갤러리. 색은 평상시에 없고 손이 닿을 때만
 * 나온다 — 액센트가 브랜딩이 아니라 보상이다.
 *
 * hover 는 색만 바꾼다. 크기가 움직이면 시선이 그리로 끌려가는데, 이 사이트에서
 * 커서가 지나가는 자리는 대부분 읽는 중인 자리다.
 *
 * 목소리는 굵기로 만든다. 디스플레이는 230~350 의 초경량에 **양수** 트래킹이고,
 * 라벨은 작고 넓게 벌어진 대문자다. 큰 글자는 가늘고 작은 글자는 벌어진다.
 * 굵은 글씨는 이 사이트에 없다.
 *
 * 서체는 Noto Sans KR 가변 한 벌이다. 라틴과 한글을 unicode-range 로 갈라
 * 두어서 영문 페이지는 한글 파일을 받지 않는다. 서브셋은 tools/fonts.py 가
 * 만들고, 빠진 글자는 CI 가 빌드 전에 잡는다.
 *
 * 테마는 [data-theme] 이 정한다. 생성된 하이라이트 스타일시트가 같은 속성을
 * 보기 때문에, prefers-color-scheme 에 맡기면 둘이 어긋날 수 있다.
 */

@font-face {
  font-family: "Display";
  src: url("/fonts/display-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20A0-20BF,
                 U+2122, U+2190-21BB, U+2212, U+2500-257F, U+25A0-25FF, U+2605-2606;
}
@font-face {
  font-family: "Display";
  src: url("/fonts/display-kr.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+1100-11FF, U+3130-318F, U+AC00-D7A3, U+FF01-FF60;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/jetbrains-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/jetbrains-mono-700.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

/* ---------- tokens ---------- */

:root {
  color-scheme: dark;

  /* 바닥에 색이 있다. 이 집 사이트들은 전부 그렇다 — ommp 는 청보라(#0A051E,
     앱의 BG_DARK 를 그대로), blog 는 청록, sqzass 문서는 청흑, 랜딩은 웜브라운.
     순수 중립 회색은 아무 말도 하지 않는다. */
  --bg-recess: #030405;
  --bg:        #07090a;
  --bg-2:      #0e1112;
  --bg-3:      #151819;

  --ink:   #eaeeec;
  --ink-2: #9ba39f;
  --ink-3: #737b77;
  --ink-4: #363c39;

  --rule:   #16191a;
  --rule-2: #232728;
  --rule-3: #363b3c;

  /* 색이 아니라 삼원소. 토큰 하나로 alpha 0.03~0.75 를 전부 낸다 —
     hex 였다면 매번 color-mix 를 거쳐야 했을 값들이다. */
  --ink-rgb: 234, 238, 236;

  /* 이 테마의 이름이자 유일한 색. 평상시에는 어디에도 없고 링크·포커스 링·
     현재 항목·손이 닿은 것에만 나온다 — 주황이면 갈 수 있는 것이거나 지금
     있는 자리다.
     지어낸 값이 아니라 About 페이지 초상의 배경에서 그대로 뽑았다
     (tools/icons.py 옆에서 샘플링, hsl 36° 75%). ommp 가 모든 색을 앱 소스에서
     가져오는 것과 같은 규칙이고, 차가운 바닥과는 보색이라 손이 닿는 자리가
     확실히 뜬다. 니어블랙 대비 9.07:1. */
  --accent: #e4a242;
  /* 액센트 위에 얹히는 잉크. 액센트가 밝은 주황이라 두 테마 모두 어둡다 —
     ::selection 처럼 액센트가 배경이 되는 자리에서 --bg 를 쓰면 라이트에서
     흰 글자가 주황 위에 놓여 읽히지 않는다. */
  --on-accent: #14140f;

  /* 선화 마크는 순수 흑 아니면 순수 백이다. 중간 회색은 비활성으로 읽힌다. */
  --mark-ink: #ffffff;

  --sans: "Display", -apple-system, BlinkMacSystemFont, "Segoe UI",
          "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* 굵기 사다리. 네 단이고 간격이 고르다 — 선택자마다 값을 적으면 사다리가
     아니라 우연의 모음이 된다. 굵은 글씨는 여전히 없다: 제일 무거운 단이 420 이다.
     트래킹은 둘뿐이다. 가는 글자를 크게 쓰면 자간이 필요하고, 두꺼워질수록
     그 필요가 줄어든다. */
  --w-display: 180;
  --w-section: 260;
  --w-item:    340;
  --w-label:   420;
  --track-air: 0.03em;
  --track-set: 0.014em;

  --radius: 2px;
  --masthead-h: 3.5rem;   /* sqzass 문서와 같은 높이. 내용이 아니라 크롬이라 얇아야 한다 */
  --gutter: clamp(1.25rem, 5vw, 3rem);
  --wide: 71rem;
  --read: 44rem;

  /* 한 박자. 값을 여기 한 번만 두면 모든 상호작용이 같은 속도로 반응하고,
     그게 "같은 물건"이라는 느낌을 만든다. hover 는 색만 바꾸므로 곡선도 하나면 된다. */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --tr-ink: 0.25s ease;
}

:root[data-theme="light"] {
  color-scheme: light;

  --bg-recess: #e9ecea;
  --bg:        #fafbfa;
  --bg-2:      #f2f4f3;
  --bg-3:      #eaeded;

  --ink:   #0e1211;
  --ink-2: #515856;
  --ink-3: #737a78;
  --ink-4: #bdc3c1;

  --rule:   #e4e8e6;
  --rule-2: #d5dad8;
  --rule-3: #bac0be;

  --ink-rgb: 14, 18, 17;
  /* 액센트는 두 테마가 같다. 밝은 바닥에서 대비가 낮아지지만, 이 색은
     평상시에 나오지 않고 hover·focus·현재 위치에만 쓰이며 그 자리에는 늘
     상태 변화가 함께 있다(밑줄, 테두리, 눈금). 색 하나로만 뜻을 전하는
     자리는 없다. */
  --accent: #e4a242;
  --mark-ink: #000000;
}

*, *::before, *::after { box-sizing: border-box; }

/* 루트가 유동이라 모든 rem 이 이미 반응형이다. 무릎은 286px 과 1143px 이다. */
html {
  font-size: clamp(16px, 15px + 0.35vw, 19px);
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  /* 루트가 이미 유동이라(clamp 16→19px) 본문까지 vw 에 매달면 두 번 조정된다.
     1100px 위에서는 어차피 최대값에 고정돼 있던 값이다. */
  font-size: 1.0625rem;
  line-height: 1.7;
  /* 한국어는 어절 안에서 줄바꿈하지 않는다. */
  word-break: keep-all;
  overflow-wrap: anywhere;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
::selection { background: var(--accent); color: var(--on-accent); }

* { scrollbar-width: thin; scrollbar-color: var(--rule-2) transparent; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--rule-2); border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-4); }


.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  padding: 0.7rem 1.1rem; background: var(--bg-2);
  border: 1px solid var(--accent); border-radius: var(--radius);
}
.skip:focus { left: 0.5rem; top: 0.5rem; }

/* ---------- layout ---------- */

.wrap {
  width: 100%;
  max-width: var(--wide);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap-read { max-width: var(--read); }
/* 읽는 폭에 날짜 거터가 나가 앉을 자리를 더한 값. 숫자를 따로 고르지 않고
   --read 에서 유도하면 본문 폭을 바꿨을 때 원장이 따라온다.
   (ch 는 컨테이너에 쓰면 본문 글꼴 기준이라 절반밖에 안 나온다 — 한 번 틀렸다.) */
.wrap-ledger { max-width: calc(var(--read) + 12rem); }

main { display: block; padding-bottom: clamp(5rem, 12vw, 9rem); }

/* ---------- masthead ---------- */

.masthead {
  position: sticky; top: 0; z-index: 30;
  height: var(--masthead-h);
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(14px) saturate(1.5);
  border-bottom: 1px solid var(--rule);
}

/* 3열 격자. 양옆 열이 1fr 로 같으므로 가운데 열이 화면 축에 정확히 앉는다.
   flex 에 margin-inline:auto 를 쓰면 브랜드와 도구 덩이의 폭 차이만큼 내비가
   밀리는데, 페이지 전체가 가운데축인 사이트에서는 그 어긋남이 보인다. */
.masthead-in {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: clamp(0.75rem, 3vw, 2rem);
  height: 100%; max-width: var(--wide); margin-inline: auto;
  padding-inline: var(--gutter);
}

.brand { display: inline-flex; align-items: center; gap: 0.55rem; flex: none; }

/* 마크는 잉크로 그린다. 원본이 흰 배경의 검은 선화라 <img> 로는 다크에서
   사라지는데, 마스크로 두면 배경색이 곧 선 색이 된다. 흑 아니면 백. */
.brand-mark {
  width: 1.9em; aspect-ratio: 128 / 123; flex: none;
  background-color: var(--mark-ink);
  -webkit-mask: url("/images/mark.png") center / contain no-repeat;
  mask: url("/images/mark.png") center / contain no-repeat;
}
@supports not (mask-image: url("")) { .brand-mark { display: none; } }

.brand-name {
  font-size: 0.82rem; font-weight: 500;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink);
}

.nav {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(1rem, 2.5vw, 2rem);
  min-width: 0; overflow-x: auto; scrollbar-width: none;
}
.nav::-webkit-scrollbar { display: none; }

/* 라벨은 작고 넓게 벌어진 대문자다. 상자도 배경도 없다. */
.nav a {
  position: relative; flex: none; padding-block: 0.4rem;
  font-size: 0.72rem; font-weight: 400;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-3);
  transition: color var(--tr-ink);
}
.nav a:hover { color: var(--ink); }
.nav a[aria-current] { color: var(--ink); }
/* 현재 위치는 1px 짧은 눈금. 밑줄은 링크의 표시이고 이건 상태의 표시다. */
.nav a[aria-current]::after {
  content: ""; position: absolute; left: 50%; bottom: -0.15rem;
  width: 1.4rem; height: 1px; margin-left: -0.7rem;
  background: var(--accent);
}

.tools { display: flex; align-items: center; justify-content: flex-end; gap: 0.9rem; }

/* 크롬 컨트롤에는 테두리가 없다. 상자를 두르면 편집물이 아니라 대시보드가 된다. */
.chip {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 0.7rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-3);
  /* 컨트롤 라벨은 낱말 하나이고, 낱말 하나는 줄바꿈하지 않는다. body 가
     overflow-wrap: anywhere 라 이걸 빼면 좁은 화면에서 "Search" 가 쪼개져
     마스트헤드 높이를 밀어 올린다. */
  white-space: nowrap;
  transition: color var(--tr-ink);
}
.chip:hover { color: var(--ink); }

/* 키를 말하는 컨트롤 하나만 진짜 키캡이 된다 — 터미널에서 모는 도구라 얇은
   테두리에 작은 반경. 네 칩에 전부 테두리를 두르면 테두리 없는 타일·원장·
   액자와 싸워서 마스트헤드가 툴바가 된다. */
.chip kbd {
  font-family: var(--mono); font-size: 0.66rem; line-height: 1; letter-spacing: 0;
  padding: 0.18rem 0.36rem;
  border: 1px solid var(--rule-2); border-radius: 3px;
  color: var(--ink-4);
  transition: border-color var(--tr-ink), color var(--tr-ink);
}
.chip:hover kbd { border-color: var(--rule-3); color: var(--ink-3); }
/* 라벨이 `Ctrl /` 에서 `⌘ /` 로 바뀌어도 폭이 안 변한다 — 누르려던 것이
   도망가면 안 된다. */
.chip-search { min-width: 4.2rem; justify-content: flex-end; }

.i-moon { display: none; }
:root[data-theme="light"] .i-sun { display: none; }
:root[data-theme="light"] .i-moon { display: inline; }

.chip-word { display: none; }

@media (max-width: 46rem) {
  .brand-name { display: none; }
  /* 키캡은 키보드가 있을 때의 안내다. 없는 화면에서는 낱말이 대신 선다. */
  .chip kbd { display: none; }
  .chip-word { display: inline; }
  .chip-search { min-width: 0; }
  /* 가운데 축을 포기한다. 양옆을 1fr 로 묶어 두면 넓은 쪽(도구 열)의 폭이
     좁은 쪽에도 강제되고, 그 낭비만큼 내비가 잘려 나간다. 축보다 다 보이는
     게 낫다 — 축은 읽는 자리를 정하지만 잘린 라벨은 길을 지운다. */
  .masthead-in { grid-template-columns: auto minmax(0, 1fr) auto; gap: 0.9rem; }
  .nav { gap: 1rem; }
}

/* 손안의 화면에서는 마스트헤드에 들어갈 자리가 모자란다. 나가는 것은 GitHub 다 —
   콜로폰에 같은 링크가 있고, 여기 남는 넷(갈래 셋과 검색)은 이 사이트 안을
   도는 데 필요한 전부다. 잘린 라벨을 남기느니 중복을 하나 뺀다. */
@media (max-width: 34rem) {
  .chip-github { display: none; }
  .masthead-in { gap: 0.75rem; }
  /* 여기서부터 내비는 밀어서 본다. 가운데 정렬로 두면 넘칠 때 왼쪽으로 흘러나간
     부분에 손이 닿지 않는다 — 스크롤은 오른쪽으로만 되기 때문이다. */
  .nav { gap: 0.85rem; justify-content: flex-start; }
}

/* ---------- page head: 가운데 축 ---------- */

.page-head {
  padding-block: clamp(3.5rem, 9vw, 7rem) clamp(2rem, 4vw, 3.5rem);
  text-align: center;
}

/* 프런트매터 펜스. 제목 블록이 끝나고 본문이 시작하는 자리에 한 페이지 한 번.
   소스 파일에서 `+++` 가 표시하는 것과 같은 경계이고, 이 사이트의 모든 페이지가
   실제로 `+++` 로 시작하는 sqzass 문서다. 장식처럼 보이는 유일한 것이며
   장식이 아니다. (sqzass 문서 시트가 같은 장치를 같은 이유로 쓴다.) */
.fence {
  font-family: var(--mono);
  font-size: 1.0625rem;
  letter-spacing: 0.1em;
  color: var(--ink-4);
  text-align: center;
  margin: 0 0 clamp(2rem, 4vw, 2.75rem);
  user-select: none;
}

.page-title {
  margin: 0;
  font-size: clamp(2rem, 1.4rem + 2.4vw, 3.2rem);
  font-weight: var(--w-display);
  letter-spacing: var(--track-air);
  line-height: 1.14;
  text-wrap: balance;
}

.page-lede {
  margin: clamp(0.9rem, 2vw, 1.4rem) auto 0;
  max-width: 52ch;
  color: var(--ink-2);
  text-wrap: pretty;
}

/* 표제 아래 한 줄. 날짜와 언어가 여기 함께 선다 — 둘 다 이 문서에 대한 사실이고,
   따로 세우면 제목과 본문 사이에 줄만 하나 더 생긴다. 비어 있으면 gap 도 여백도
   없다(빈 flex 컨테이너라 높이가 0 이다). */
.page-meta {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: center; gap: 1.1rem;
  margin: 1.2rem 0 0;
  font-family: var(--mono); font-size: 0.74rem;
  letter-spacing: 0.08em; font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}
.page-meta:empty { margin: 0; }

/* ---------- language switch ---------- */

/* 두 글자 코드 둘. 사이에 구분자를 두지 않는다 — 간격이 이미 나누고 있고,
   가운뎃점이나 슬래시를 넣으면 두 낱말이 한 낱말처럼 붙어 보인다.

   현재 언어가 제일 밝다. 크롬의 현재 항목이 밝아지는 것과 같은 규칙이고,
   반대로 두면 "지금 어디에 있는지"를 알려고 두 회색을 견줘야 한다.
   갈 수 있는 쪽에는 밑줄 자리가 비어 있다가 손이 닿으면 액센트로 채워진다 —
   색만으로 뜻을 전하지 않는다는 이 사이트의 규칙 그대로다. */
/* 자기 크기를 스스로 갖는다. .page-meta 안에도 서고 프로젝트 히어로의 배지 줄
   아래에도 서는데, 부모마다 크기가 달라지면 같은 컨트롤이 페이지마다 다른
   물건으로 보인다. */
.langs {
  display: inline-flex; gap: 0.55rem;
  font-family: var(--mono); font-size: 0.7rem;
  letter-spacing: 0.16em; text-transform: uppercase;
}
/* 히어로에서는 배지 줄 바로 아래 붙는다 — 둘 다 이 프로젝트에 대한 사실이다. */
.hero-main > .langs { margin-top: 0.85rem; }
.lang-cur { color: var(--ink); }
/* 갈 수 있는 쪽은 이 사이트의 링크 단이다(--ink-3). --ink-4 는 날짜와 펜스의
   단이라 "있지만 누를 것은 아닌" 자리이고, 라이트 테마에서는 1.7:1 로 사실상
   안 보인다. 지금 판(--ink)과의 차이는 이 단에서도 충분히 크다. */
.langs a {
  color: var(--ink-3);
  border-bottom: 1px solid transparent;
  transition: color var(--tr-ink), border-color var(--tr-ink);
}
.langs a:hover { color: var(--ink-2); border-bottom-color: var(--accent); }

.eyebrow {
  margin: 0 0 0.9rem;
  font-size: 0.7rem; font-weight: 400;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-3);
}

.crumbs { margin-bottom: 0.9rem; font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; }
.crumbs a { color: var(--ink-3); transition: color var(--tr-ink); }
.crumbs a:hover { color: var(--accent); }

/* ---------- wing title: 섹션 머리 ---------- */

/* 가운데 제목 양옆으로 짧은 헤어라인. 페이지를 연으로 끊는 장치이고,
   위 여백이 넉넉해야 연이 연으로 읽힌다. */
/* 갈래 머리. 옆줄은 없다 — 한 가지 일에 한 가지 장치이고, 위아래 여백이
   이미 연을 끊고 있다. 좁아지면 줄이는 게 아니라 뺀다. */
/* 갈래 머리는 한 줄이다: 이름, 그 갈래로 가는 문까지 이어지는 헤어라인, 그리고
   문. 헤어라인은 장식이 아니라 둘을 한 줄의 양끝으로 묶는 것이고, 그래서 이름과
   문 사이에 무엇이 오든 둘은 같은 줄에 있다.
   아래 내용이 전부 왼쪽 정렬(카드의 라벨, 원장의 날짜 거터)이라 머리도 왼쪽에서
   시작한다. 가운데축은 히어로와 글 한 편의 것이고, 갈래의 머리는 목록의 것이다. */
.room-head {
  display: flex; align-items: center;
  gap: clamp(0.9rem, 2vw, 1.5rem);
  margin: clamp(4.5rem, 11vw, 7rem) 0 clamp(1.5rem, 3.5vw, 2.25rem);
}
.room-head h2 {
  margin: 0; flex: none;
  font-size: clamp(1.05rem, 0.95rem + 0.55vw, 1.3rem);
  font-weight: var(--w-section); letter-spacing: var(--track-air); line-height: 1.2;
}
/* 남는 자리를 전부 먹는다. 문이 없는 갈래(프로젝트 묶음)에서는 끝까지 간다. */
.room-rule { flex: 1 1 auto; min-width: 1rem; height: 1px; background: var(--rule); }
.room-head-sub {
  margin: -0.75rem 0 clamp(1.75rem, 4vw, 2.5rem);
  max-width: 54ch;
  font-size: 0.92rem; color: var(--ink-3);
}


/* ---------- home hero ---------- */

/* 첫 화면 전체를 쓴다. 스크롤 전에 사이트의 장르를 정하는 자리다. */
.hero-home {
  min-height: calc(100svh - var(--masthead-h));
  display: grid; place-items: center; text-align: center;
  padding-block: clamp(2rem, 6vw, 4rem);
}
.hero-home > .wrap { display: grid; justify-items: center; }

.hero-mark {
  width: clamp(3.4rem, 8vw, 5rem); aspect-ratio: 128 / 123;
  background-color: var(--mark-ink);
  -webkit-mask: url("/images/mark.png") center / contain no-repeat;
  mask: url("/images/mark.png") center / contain no-repeat;
  margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
}

.statement {
  margin: 0; max-width: 20ch;
  font-size: clamp(2.2rem, 1.3rem + 3.6vw, 4.2rem);
  font-weight: var(--w-display); letter-spacing: var(--track-air); line-height: 1.1;
  text-wrap: balance;
}

.statement-sub {
  margin: clamp(1.2rem, 3vw, 1.8rem) auto 0;
  max-width: 44ch; color: var(--ink-2); text-wrap: pretty;
}

/* 문. 채운 알약이 아니라 밑줄 그은 라벨이다. */
.door {
  display: inline-block; margin-top: clamp(1.8rem, 4vw, 2.75rem);
  padding-bottom: 0.35rem;
  font-size: 0.72rem; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-2);
  border-bottom: 1px solid var(--rule-3);
  transition: color var(--tr-ink), border-color var(--tr-ink);
}
.door:hover { color: var(--ink); border-color: var(--accent); }

/* 페이지가 열릴 때 한 번. 스크롤에 반응해 숨었다 나타나지 않는다 —
   읽던 자리로 돌아왔을 때 사라지는 것은 고장으로 보인다. */
@keyframes rise { from { opacity: 0; transform: translateY(0.75rem); } to { opacity: 1; transform: none; } }
.js .hero-home [data-rise] { animation: rise 0.9s var(--ease) both; }
.js .hero-home [data-rise="1"] { animation-delay: 0.12s; }
.js .hero-home [data-rise="2"] { animation-delay: 0.28s; }
.js .hero-home [data-rise="3"] { animation-delay: 0.42s; }

/* ---------- home rooms ---------- */

/* 홈의 갈래는 .wrap 보다 좁다. 1440 에서 1102px — 참조 카드(1123)와 같은 자리다.
   머리도 카드도 타일도 이 폭을 쓰므로 셋의 왼쪽 끝과 오른쪽 끝이 같다. */
.room { max-width: calc(58rem + 2 * var(--gutter)); padding-block: 0; }

/* 갈래의 위 여백은 그 갈래의 첫 물건이 진다. 전에는 언제나 머리가 졌는데,
   글 갈래에서는 머리가 카드 뒤로 갔다. */
.room > .feature:first-child { margin-top: clamp(4.5rem, 11vw, 7rem); }
/* 카드 뒤에 오는 머리는 갈래를 여는 것이 아니라 그 아래 목록을 여는 것이라
   덜 띄운다. 갈래 사이의 거리와 갈래 안의 거리는 달라야 한다. */
.feature + .room-head { margin-top: clamp(2.5rem, 6vw, 3.5rem); }

/* 문은 머리의 오른쪽 끝에 선다. 밑줄은 이 사이트의 문(.door)과 같은 물건이다. */
.room-door {
  flex: none;
  padding-bottom: 0.25rem;
  font-size: 0.7rem; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-3);
  border-bottom: 1px solid var(--rule-2);
  transition: color var(--tr-ink), border-color var(--tr-ink);
}
.room-door:hover { color: var(--ink); border-bottom-color: var(--accent); }

/* ---------- featured frames ---------- */

/* 액자는 테두리가 없다. 그림에만 테두리가 있고 캡션은 그 아래 떠 있다.
   flex 라 셋이든 넷이든 마지막 줄에 하나만 남지 않는다. */
.frames {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: clamp(1.5rem, 3vw, 2.25rem);
}

.frame {
  flex: 1 1 220px; max-width: 400px;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  background: transparent; border: 0;
}

.frame-media {
  width: 100%; aspect-ratio: 16 / 10;
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  overflow: hidden; display: grid; place-items: center;
  background: var(--bg-2);
  transition: border-color var(--tr-ink);
}
.frame:hover .frame-media { border-color: var(--rule-3); }

/* 그림은 제 색으로 쉰다. 탈색해 두었다가 손이 닿으면 돌려주는 건 다른 집의
   장치였다 — 여기서는 ommp 가 4K 컬러 스크린샷을 그대로 싣는 쪽이 기준이다. */
.frame-media img { width: 100%; height: 100%; object-fit: cover; }

.frame-mark {
  width: 42%; aspect-ratio: 1;
  background-color: var(--mark-ink);
  -webkit-mask: var(--mark-src) center / contain no-repeat;
  mask: var(--mark-src) center / contain no-repeat;
  opacity: 0.72; transition: opacity var(--tr-ink);
}
.frame:hover .frame-mark { opacity: 1; }

.frame-title {
  display: flex; align-items: center; justify-content: center; gap: 0.5rem;
  margin: clamp(0.9rem, 2vw, 1.25rem) 0 0;
  font-size: 1.25rem; font-weight: var(--w-item); letter-spacing: var(--track-set);
}
.frame-title a { transition: color var(--tr-ink); }
.frame:hover .frame-title a { color: var(--accent); }

/* 이름 옆 작은 마크. 크기를 em 으로 잡아 제목이 커지면 같이 커진다. */
.chip-mark {
  width: 1.55em; height: 1.55em; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  background: var(--bg-2);
}
.chip-mark::before {
  content: ""; width: 74%; height: 74%;
  background-color: var(--mark-ink);
  -webkit-mask: var(--mark-src) center / contain no-repeat;
  mask: var(--mark-src) center / contain no-repeat;
}
.chip-mark img { width: 74%; height: 74%; object-fit: contain; }

.frame-blurb {
  margin: 0.6rem 0 0; max-width: 40ch;
  font-size: 0.9rem; color: var(--ink-3); text-wrap: pretty;
}

.frame-meta {
  display: flex; align-items: center; justify-content: center;
  gap: 0.9rem; margin-top: 0.9rem;
}

/* ---------- no image ---------- */

/* 그림이 아직 없는 자리. 회색 상자로 때우지 않고 비었다고 말한다 —
   나중에 무엇을 채워야 하는지가 화면에 보여야 한다. */
.noimg {
  width: 100%; height: 100%;
  display: grid; place-items: center;
  background:
    repeating-linear-gradient(45deg,
      transparent, transparent 7px,
      rgba(var(--ink-rgb), 0.022) 7px, rgba(var(--ink-rgb), 0.022) 14px);
}
.noimg::after {
  content: "no image";
  font-family: var(--mono); font-size: 0.62rem;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--ink-4);
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  padding: 0.3rem 0.6rem;
}

/* ---------- project tiles ---------- */

/* 카드다. 테두리와 바닥을 갖고, 글은 왼쪽에서 시작한다.
   테두리가 없던 때는 이 여섯 개가 페이지에서 유일하게 액자 없는 물건이라,
   위의 카드 하나만 상자로 떠 있고 아래는 글자만 떠다녔다. 갈래 머리가 왼쪽에서
   시작하는데 그 아래 내용이 가운데 정렬인 것도 같은 어긋남이었다. */
/* 트랙에 상한을 두고 justify-content 로 가운데 모은다. `1fr` 로 두면 항목이
   두 개일 때 3열 격자의 왼쪽 두 칸에 붙어 페이지 축이 어긋난다 — 갈래마다
   개수가 다른 목록에서는 이게 항상 일어난다. */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 21rem));
  justify-content: center;
  gap: clamp(0.5rem, 1.5vw, 1rem);
}

.tile {
  position: relative; isolation: isolate;
  display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start;
  gap: 0.5rem; text-align: left;
  padding: clamp(1.25rem, 3vw, 1.75rem);
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--bg-2);
  transition: border-color var(--tr-ink);
}
/* 손이 닿으면 테두리가 한 단 오른다 — 맨 위 카드와 같은 응답이다. */
.tile:hover { border-color: var(--rule-2); }
/* 누르면 눌린다. 빛이 번지는 대신 물건이 압축된다 — 이 집의 이름들이 전부
   모음을 눌러 뺀 것이고(sqzer, sqzass, ommp), 랜딩이 이미 같은 값을 쓴다.
   0.988 은 눈이 알아채되 글자가 흐려지지는 않는 지점이다. */

.tile-title {
  display: flex; align-items: center; gap: 0.45rem;
  margin: 0; font-size: 1.05rem; font-weight: var(--w-item); letter-spacing: var(--track-set);
  color: var(--ink-2); transition: color var(--tr-ink);
}
.tile:hover .tile-title { color: var(--ink); }
.tile-title .chip-mark { width: 1.4em; height: 1.4em; }

.tile-blurb {
  margin: 0;
  font-size: 0.85rem; line-height: 1.6; color: var(--ink-3); text-wrap: pretty;
}

/* 사실 줄은 카드 아래쪽에 붙는다. margin-top:auto 가 그 위 여백을 전부 먹어서,
   설명 길이가 달라도 여섯 장의 사실 줄이 같은 높이에 선다. */
.tile-meta {
  display: flex; align-items: center; gap: 0.7rem;
  margin-top: auto; padding-top: 0.35rem;
  font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.06em;
  color: var(--ink-4); font-variant-numeric: tabular-nums;
  transition: color var(--tr-ink);
}
.tile:hover .tile-meta { color: var(--ink-3); }

.tile-links { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.1rem; }
.tile-links a, .frame-meta a {
  position: relative; z-index: 2;
  display: inline-flex; color: var(--ink-4);
  transition: color var(--tr-ink);
}
.tile-links a:hover, .frame-meta a:hover { color: var(--accent); }

/* 아이콘만 있는 링크에는 이름이 안 보인다. title 속성은 브라우저가 1초쯤 뒤에
   자기 모양으로 띄우는데, 그 지연과 모양이 사이트와 무관하다. 같은 값을
   data-tooltip 으로도 주고 우리가 그린다. aria-label 이 접근성을 이미 책임지므로
   이건 순수하게 눈에 보이는 쪽이고, 그래서 aria-hidden 이다. */
[data-tooltip] { position: relative; }
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute; bottom: calc(100% + 0.5rem); left: 50%;
  transform: translateX(-50%);
  padding: 0.28rem 0.5rem; white-space: nowrap; pointer-events: none;
  background: var(--bg-3); color: var(--ink-2);
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  font-size: 0.62rem; letter-spacing: 0.14em; text-transform: uppercase;
  opacity: 0; transition: opacity var(--tr-ink);
}
[data-tooltip]:hover::after, [data-tooltip]:focus-visible::after { opacity: 1; }
html[lang="ko"] [data-tooltip]::after { letter-spacing: 0; text-transform: none; }

/* 타일 전체가 상세 페이지로 가는 링크. 아이콘만 위로 올린다. */
.tile-link::after { content: ""; position: absolute; inset: 0; }

/* 언어 배지 — 작고 넓게 벌어진 대문자. */
.lang {
  font-family: var(--mono); font-size: 0.62rem;
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-3);
}
.stars { font-variant-numeric: tabular-nums; }

/* ---------- project mosaic ---------- */

/* 첫 장이 크고 나머지 셋이 옆에 쌓인다. 대칭 격자는 여섯 개가 다 같은 무게라고
   말하는데, 갈래에는 앞에 세울 것이 있다.

   그림이 있는 프로젝트는 둘뿐이다. 없는 것은 자기 색으로 선다 — 프로젝트마다
   이미 색이 하나씩 정해져 있고(상세 페이지의 --accent 와 같은 값), 그 색이
   "그림 없음" 판보다 그 프로젝트에 대해 더 많이 말한다. 그림이 생기면 카탈로그의
   cover 한 줄로 들어온다. */
.mosaic {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  grid-template-rows: repeat(3, minmax(0, 1fr));
  gap: clamp(0.5rem, 1.5vw, 1rem);
}
/* 첫 장이 왼쪽 열을 통째로 쓴다. :first-child 라 개수가 넷이 아니어도 안 깨진다. */
.mosaic > :first-child { grid-row: 1 / -1; }

.mtile {
  position: relative; display: flex; align-items: flex-end;
  overflow: hidden;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--bg-2);
  transition: border-color var(--tr-ink);
}
.mtile:hover { border-color: var(--rule-2); }
/* 작은 장의 키를 여기서 정한다. 큰 장은 셋의 합만큼 자라므로 자기 비율이 없다. */
.mosaic > :not(:first-child) { aspect-ratio: 2.1 / 1; }

/* 바탕. 그림이 있으면 그림이, 없으면 그 프로젝트의 색이 깔린다.

   평상시에는 색이 빠져 있고 손이 닿으면 돌아온다. 이 시트의 첫 줄에 적힌
   전제가 그것이다 — "색은 평상시에 없고 손이 닿을 때만 나온다". 그림에까지
   그 규칙을 적용한 것이고, 크기나 자리는 그대로이므로 hover 가 색만 바꾼다는
   규칙도 지켜진다. 박자는 이 시트의 유일한 박자다.
   (한때 이 탈색을 걷어냈던 적이 있다. 그때는 참조에서 그대로 가져온 값이라
   뺐고, 지금은 이 사이트의 전제를 그림까지 밀고 간 것이라 뜻이 다르다.) */
.mtile-art {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--tint) 30%, var(--bg-2));
  filter: grayscale(1);
  transition: filter var(--tr-ink);
}
.mtile:hover .mtile-art, .mtile:focus-visible .mtile-art { filter: grayscale(0); }
.mtile-art img { width: 100%; height: 100%; object-fit: cover; }
/* 마크는 잉크가 아니라 그 프로젝트의 색을 진하게 쓴 것이다 — 바탕과 같은 색의
   두 단이라 한 물건으로 읽힌다. */
.mtile-mark {
  width: 42%; aspect-ratio: 1;
  background-color: color-mix(in srgb, var(--tint) 62%, var(--ink));
  -webkit-mask: var(--mark-src) center / contain no-repeat;
  mask: var(--mark-src) center / contain no-repeat;
}

/* 글은 바닥에 앉고, 그 아래에서 바탕색이 올라와 그림을 덮는다. 밝은 테마에서는
   밝은 장막이고 어두운 테마에서는 어두운 장막이라, 글은 두 테마 모두에서 --ink
   한 색으로 읽힌다. 참조 사이트는 다크 전용이라 흰 글자 한 벌로 끝난다. */
.mtile-body {
  position: relative; z-index: 1; width: 100%;
  display: flex; flex-direction: column; gap: 0.3rem;
  padding: clamp(1rem, 2.4vw, 1.6rem);
  /* 글이 앉는 띠는 바탕색으로 꽉 차 있고 그 위에서 풀린다. 그라데이션이 글자
     높이 안에서 투명해지면 밝은 그림 위의 밝은 글자가 되는데, ommp 의 표지가
     정확히 그랬다. 두 테마 모두 --bg 로 가므로 글은 --ink 한 색이면 된다. */
  background: linear-gradient(to top,
    var(--bg) 0%,
    var(--bg) 42%,
    color-mix(in srgb, var(--bg) 72%, transparent) 72%,
    transparent 100%);
}
.mtile-title {
  font-size: clamp(0.95rem, 0.9rem + 0.3vw, 1.05rem);
  font-weight: var(--w-item); letter-spacing: var(--track-set);
  color: var(--ink);
}
.mtile-blurb {
  max-width: 44ch; font-size: 0.8rem; line-height: 1.5;
  color: var(--ink-2); text-wrap: pretty;
}
.mtile-stars {
  margin-top: 0.15rem;
  font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums; color: var(--ink-3);
}
/* 큰 장은 글도 한 단 크다 — 크기가 순서를 말한다. */
.mosaic > :first-child .mtile-title { font-size: clamp(1.1rem, 1rem + 0.5vw, 1.35rem); }

/* 여기서부터 작은 장의 키는 글이 정한다. 비율을 고정해 두면 폭이 줄면서 상자가
   글보다 먼저 작아져 잘린다 — 카드에서 겪은 것과 같은 어긋남이다. 그림은 어차피
   잘리는 물건이므로 여기서는 글이 이기게 둔다. 큰 장은 셋의 합만큼 자란다. */
@media (max-width: 72rem) {
  .mosaic > :not(:first-child) { aspect-ratio: auto; }
}

/* 좁아지면 한 열로 쌓고 전부 같은 비율이 된다. 두 열로 두면 작은 장이 글을
   담기에 좁아진다. */
@media (max-width: 46rem) {
  .mosaic { grid-template-columns: 1fr; grid-template-rows: none; }
  .mosaic > :first-child { grid-row: auto; }
  .mosaic > * { aspect-ratio: 2 / 1; }
}

/* ---------- ledger ---------- */

.ledger { list-style: none; margin: 0; padding: 0; }

.ledger-year {
  margin-top: clamp(2.5rem, 6vw, 4rem);
  text-align: center;
  font-family: var(--mono); font-size: 0.68rem;
  letter-spacing: 0.22em; color: var(--ink-4);
}
.ledger-year:first-child { margin-top: 0; }

/* 행 사이에 선이 없다. 간격이 나눈다.

   손이 닿아도 바닥이 안 바뀐다. 회색 판이 깔리면 그 행만 다른 재질이 된 것처럼
   보이고, 목록을 훑는 동안 판이 행에서 행으로 따라다닌다 — 읽는 자리에서
   움직이는 것이 하나 생기는 셈이다. 이 사이트의 hover 는 색만 바꾼다는 규칙에
   맞춰, 여기서도 바뀌는 것은 날짜와 제목의 잉크뿐이다.

   가로 패딩과 같은 크기의 음수 마진이 남아 있는 것은 판을 깔기 위해서가 아니라
   누를 수 있는 자리를 글자 폭보다 넓게 두기 위해서다. 글은 제자리에 선다.
   키보드 포커스는 전역 :focus-visible 링이 진다. */
.ledger-link {
  display: grid;
  grid-template-columns: clamp(5rem, 11vw, 8rem) minmax(0, 1fr);
  align-items: baseline; gap: clamp(1rem, 2.5vw, 2rem);
  padding: 1.1rem 1rem; margin-inline: -1rem;
}

.ledger-date {
  font-family: var(--mono); font-size: 0.72rem;
  letter-spacing: 0.12em; text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--ink-4); transition: color var(--tr-ink);
}
.ledger-link:hover .ledger-date { color: var(--accent); }

.ledger-body { display: block; min-width: 0; }
.ledger-title {
  display: block;
  font-size: clamp(1.15rem, 1rem + 0.7vw, 1.55rem);
  font-weight: var(--w-item); letter-spacing: var(--track-set); line-height: 1.25;
  color: var(--ink-2); transition: color var(--tr-ink); text-wrap: balance;
  /* 홈의 글 갈래가 넓은 방으로 옮겨 가면서 이 열이 1080px 까지 열렸다. 제목은
     한 줄로 읽는 물건이라 폭에 상한이 필요하다 — 설명(58ch)에는 이미 있었다. */
  max-width: 42ch;
}
.ledger-link:hover .ledger-title { color: var(--ink); }
.ledger-desc {
  display: block; margin-top: 0.3rem; max-width: 58ch;
  font-size: 0.88rem; color: var(--ink-3);
}

@media (max-width: 40rem) {
  .ledger-link { grid-template-columns: 1fr; gap: 0.5rem; padding-inline: 0; margin-inline: 0; }
}

/* 홈의 맨 위 글. 그림과 이름표가 테두리 하나 안에 같이 있다 — 미술관 라벨과
   작품의 관계다. 전에는 그림을 가운데 세우고 글을 그 아래 따로 세웠는데, 폭도
   정렬도 달라서 두 물건으로 읽혔다.

   /projects/ 의 액자는 테두리를 그림에만 두르고 캡션을 아래에 띄운다. 그건 여러
   점이 나란히 걸릴 때의 문법이고, 여기는 한 점뿐이라 라벨이 붙어 있어야 한 작품이
   된다.

   호버는 색만 바꾼다: 테두리가 한 단, 날짜가 액센트로, 제목이 잉크로. 원장의
   행과 같은 응답이다. */
.feature {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  /* 카드의 크기는 글이 정하지 않는다. 어느 글이 맨 위에 오든 같은 상자다.
     8/3 은 지어낸 값이 아니라 두 열에서 따라 나온다: 그림이 카드 폭의 0.6 을
     차지하고 그 그림이 16:10 이므로 카드 높이 = 0.6W ÷ 1.6 = W ÷ 2.667 이다.
     여기 비율을 걸어 두면 이름표가 행을 늘리지 못하고, 그림은 자기 칸을 채우기만
     하면 되므로 aspect-ratio 를 따로 갖지 않는다. */
  aspect-ratio: 8 / 3;
  /* 이 카드 안의 크기는 창이 아니라 **카드**를 따른다. 아래 cqw 가 그 단위다.
     창에 매달았더니 카드는 좁아지는데 글자는 그만큼 안 줄어서, 920px 근처에서
     제목의 디센더와 설명 둘째 줄이 상자 밖으로 밀려 잘렸다. 상자와 글자가 같은
     수에 매여 있으면 그 어긋남이 생길 자리가 없다. */
  container-type: inline-size;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-2);
  text-align: left;
  transition: border-color var(--tr-ink);
}
.feature:hover { border-color: var(--rule-2); }
/* 미리보기가 없는 글은 한 칸이다. 빈 칸을 남기면 그 글만 뭔가 빠진 것처럼 보인다. */
.feature:not(:has(.feature-media)) { grid-template-columns: 1fr; }

/* 그림은 자기 칸을 채운다. 높이는 이름표가 정하고 그림이 거기에 맞춰 잘린다 —
   16:9 를 고정하면 제목이 길어질 때 두 칸의 키가 어긋난다.
   손이 닿으면 안에서 돌아간다. 상자는 자리도 크기도 안 바꾸므로, 호버에
   레이아웃이 움직이지 않는다는 규칙은 그대로다.
   poster 는 영상의 첫 프레임이다. 시작할 때도 되감을 때도 같은 그림이라 이음매가
   없다. */
.feature-media {
  display: block; margin: 0;
  /* 높이 바닥을 따로 두지 않는다. width/height 속성이 16:9 를 알려 주므로 그
     비율이 그대로 행 높이의 바닥이 되고, 라벨이 더 길면 라벨이 이긴다. 예전의
     clamp 는 어느 폭에서도 이 값을 넘지 못해서 아무 일도 하지 않았다. */
  width: 100%; height: 100%;
  /* 16:10 은 이 사이트의 그림 비율이다 — /projects/ 의 .frame-media 가 같은 값을
     쓴다. 이 카드만 <video> 의 width/height 속성이 주는 16:9 를 그대로 행 높이로
     써서, 카드가 3.09:1 로 납작했다(참조는 2.66:1). CSS 비율이 내재 비율을
     덮으므로 행은 이제 max(이름표의 자연 높이, 그림 폭 ÷ 1.6) 이고, 제목이
     길어지면 이름표가 이긴다.
     좌우가 잘리는데 이 포스터는 만다라도 인물도 가운데 있어서 잘려 나가는 것은
     배경이다. 반대로 납작하게 두면 세로가 잘리고, 그때 잘리는 것은 머리다. */
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-right: 1px solid var(--rule);
  background: var(--bg-3);
}

.feature-label {
  display: flex; flex-direction: column; justify-content: center;
  gap: clamp(0.7rem, 1.9cqw, 1.2rem); min-width: 0;
  /* 상자가 고정이므로 넘칠 수 있는 쪽은 글이다. 마지막 방어선. */
  overflow: hidden;
  padding: clamp(1.1rem, 3.8cqw, 2.4rem);
}
.feature-date {
  font-family: var(--mono); font-size: 0.7rem;
  letter-spacing: 0.14em; font-variant-numeric: tabular-nums;
  color: var(--ink-4); transition: color var(--tr-ink);
}
.feature:hover .feature-date { color: var(--accent); }
.feature-title {
  margin: 0;
  /* 카드 폭의 3.7% — 1102px 카드에서 40.8px 이고, 이름표의 측정폭 353px 중
     제목이 약 306px 을 쓴다. 제목이 자기 열을 거의 채우는 것이 이 카드가
     헤드라인처럼 읽히는 이유다(참조는 353 중 353).
     cqw 라 카드가 좁아지면 글자도 같은 비율로 좁아진다. rem 하한은 아주 좁은
     카드에서 읽히는 크기의 바닥이다. */
  font-size: clamp(1.25rem, 3.7cqw, 2.3rem);
  font-weight: var(--w-section); letter-spacing: var(--track-air); line-height: 1.2;
  color: var(--ink-2); transition: color var(--tr-ink);
  /* 세 줄까지. 1440 에서 이름표 안쪽이 372px 이고 날짜·설명·문·간격이 125px 을
     쓰므로 제목에 247px 이 남는다 — 세 줄이 178px 이라 어떤 제목이 와도 들어온다.

     -webkit-line-clamp 를 쓰지 않는다. 이 조합에서 display 가 flow-root 로
     계산돼 clamp 자체가 죽고 overflow:hidden 만 남았는데, 그러면 상자 높이가
     line-height(1.2em)라 글자의 실제 줄상자(1.44em)가 6px 잘린다 — "Beginning"
     의 g 아래가 그렇게 날아갔다. 줄 수는 max-height 로 세고, padding-bottom 이
     디센더가 앉을 자리를 만든다. 둘 다 em 이라 크기를 바꿔도 따라온다. */
  padding-bottom: 0.25em;
  max-height: calc(3 * 1.2em + 0.25em);
  overflow: hidden;
  /* 이 시트의 모든 제목이 balance 다. 한때 여기만 pretty 로 바꿔 두고 "balance 는
     제목을 한 줄로 붙인다"고 적었는데, 재 보니 39자 제목에서 둘 다 세 줄이고
     balance 쪽이 줄 길이만 고르게 맞춘다. 틀린 이유로 예외를 만들 뻔했다. */
  text-wrap: balance;
}
.feature:hover .feature-title { color: var(--ink); }
.feature-desc {
  margin: 0; max-width: 38ch; color: var(--ink-3); text-wrap: pretty;
  font-size: clamp(0.85rem, 1.65cqw, 1rem);
  /* 두 줄까지. 줄 간격이 1.65em 이라 글자 줄상자(1.44em)보다 넉넉해서 여기는
     따로 자리를 만들 필요가 없다. */
  line-height: 1.65;
  max-height: calc(2 * 1.65em);
  overflow: hidden;
}

/* 글 덩이의 아래 끝. 문(.door)과 같은 물건이다 — 밑줄 그은 마이크로캡스.
   align-self 가 없으면 flex 열에서 가로로 늘어나 밑줄이 라벨 폭을 가로지른다. */
.feature-cta {
  align-self: start;
  padding-bottom: 0.3rem;
  font-size: 0.7rem; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-3);
  border-bottom: 1px solid var(--rule-2);
  transition: color var(--tr-ink), border-color var(--tr-ink);
}
/* 밑줄은 잉크 사다리에서 한 단만 오른다. 액센트는 이 카드에 하나뿐이고 날짜가
   그걸 갖는다 — 위 규칙에 그렇게 적어 놓고 여기서 두 번째를 켰었다. */
.feature:hover .feature-cta { color: var(--ink-2); border-bottom-color: var(--rule-3); }

/* 좁아지면 그림이 위로 올라간다. 옆으로 두면 두 칸 다 못 쓸 만큼 좁아진다.
   64rem 인 이유: 그 아래에서는 고정 8/3 상자에 네 줄이 안 들어간다. 상자를
   늘리는 대신 쌓는다 — 쌓인 카드는 글이 크기를 정해도 되고, 그게 이 폭에서
   맞는 답이다. */
@media (max-width: 56rem) {
  .feature { grid-template-columns: 1fr; aspect-ratio: auto; }
  .feature-media {
    aspect-ratio: 16 / 10;
    border-right: 0; border-bottom: 1px solid var(--rule);
  }
}

/* ---------- buttons ---------- */

.btn-row { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; margin-top: 1.75rem; }

.btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  height: 2.5rem; padding-inline: 1.15rem;
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-2);
  transition: color var(--tr-ink), border-color var(--tr-ink), background var(--tr-ink);
}
/* 채운 버튼은 잉크로 채운다. 액센트로 채우면 그게 첫 화면에서 제일 큰 색이 된다. */
.btn-primary { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.btn-primary:hover { background: var(--ink); filter: brightness(1.08); }
.btn-ghost:hover { color: var(--ink); border-color: var(--rule-3); }
.btn-text { border-color: transparent; padding-inline: 0.35rem; }
.btn-text:hover { color: var(--accent); }

.back {
  display: inline-flex; align-items: center; gap: 0.45rem;
  margin-top: 2rem;
  font-size: 0.7rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-4);
  transition: color var(--tr-ink);
}
.back:hover { color: var(--ink-2); }

/* ---------- project detail ---------- */

/* 프로젝트마다 자기 색조를 갖는다. 전역 --accent 를 이 페이지 안에서만
   덮으므로 eyebrow 대시·특징 번호·링크·포커스가 한꺼번에 따라간다. 값이 없는
   프로젝트는 인라인 style 이 아예 안 붙어서 사이트 기본값을 쓴다.
   대비 범위는 좁게 잡는다 — 중간 명도, 낮은 채도. 니어블랙 위에서 읽히면서도
   페이지를 자기 색으로 물들이지는 않는 띠다. */
.project { padding-bottom: clamp(3rem, 8vw, 6rem); }

/* 브리프는 읽는 폭 안에 살고 제목도 한 단계 작다. 등급 차이가 열 개수가
   아니라 타이포로 보여야 한다 — 그러지 않으면 "같은 페이지, 더 짧은 것"이다. */
.project[data-tier="brief"] .hero-name { font-size: clamp(2rem, 1.6rem + 1.6vw, 2.8rem); }
.project[data-tier="brief"] .hero { border-bottom: 0; padding-bottom: clamp(1.5rem, 3vw, 2rem); }

.hero {
  display: grid; grid-template-columns: 1.35fr 1fr;
  gap: clamp(1.5rem, 5vw, 4rem); align-items: center;
  padding-block: clamp(2rem, 5vw, 3.5rem) clamp(2.5rem, 6vw, 4rem);
  border-bottom: 1px solid var(--rule);
}
.hero-main { min-width: 0; }

/* eyebrow 앞의 액센트 대시. 제품 명패의 시작을 표시한다. */
.hero .eyebrow { display: flex; align-items: center; gap: 0.75rem; }
.hero .eyebrow::before {
  content: ""; width: 1.75rem; height: 1px; background: var(--accent); flex: none;
}

.hero-name {
  display: flex; align-items: center; gap: 0.6rem; margin: 0;
  font-size: clamp(2.6rem, 1.9rem + 2.6vw, 3.8rem);
  font-weight: var(--w-display); letter-spacing: var(--track-air); line-height: 1.05;
}
.hero-name .chip-mark { width: 1.05em; height: 1.05em; }

.hero-tagline { margin: 1.1rem 0 0; max-width: 46ch; color: var(--ink-2); text-wrap: pretty; }

.badges { display: flex; flex-wrap: wrap; gap: 1.25rem; margin: 1.5rem 0 0; padding: 0; list-style: none; }
.badges li {
  font-family: var(--mono); font-size: 0.66rem;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-4);
}

.hero .btn-row { justify-content: flex-start; }

.hero-aside { display: grid; place-items: center; }
.medallion {
  display: grid; place-items: center;
  width: min(100%, 20rem); aspect-ratio: 1;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--bg-2); overflow: hidden;
}
.medallion img { width: 76%; height: 76%; object-fit: contain; }
.medallion-mark::before {
  content: ""; width: 72%; height: 72%;
  background-color: var(--mark-ink);
  -webkit-mask: var(--mark-src) center / contain no-repeat;
  mask: var(--mark-src) center / contain no-repeat;
}

@media (max-width: 52rem) {
  .hero { grid-template-columns: minmax(0, 1fr); text-align: center; }
  .hero .eyebrow, .hero-name, .hero .btn-row { justify-content: center; }
  .hero-tagline, .badges { margin-inline: auto; justify-content: center; }
  /* 좁을 때 옆칸은 지우지 않고 위로 올린다. 지우면 그 프로젝트만 그림이 없는
     것처럼 보인다. */
  .hero-aside { order: -1; }
  .medallion { width: min(60%, 13rem); }
}

/* ---------- facts ---------- */

.facts {
  display: flex; flex-wrap: wrap; justify-content: center;
  margin-block: clamp(2rem, 5vw, 3rem);
  padding-block: clamp(1.5rem, 3vw, 2rem);
  border-bottom: 1px solid var(--rule);
}
.fact {
  flex: 1 1 9rem; display: flex; flex-direction: column; gap: 0.3rem;
  align-items: center; text-align: center; padding-inline: 1rem;
  border-left: 1px solid var(--rule);
}
.fact:first-child { border-left: 0; }
.fact-value {
  font-size: clamp(1.9rem, 1.5rem + 1.6vw, 2.9rem);
  font-weight: var(--w-display); letter-spacing: 0.005em; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.fact-label {
  font-size: 0.64rem; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-4);
}

/* ---------- lattice ---------- */

.block { padding-block: clamp(2.5rem, 6vw, 4rem); border-bottom: 1px solid var(--rule); }
.block:last-child { border-bottom: 0; }
.block-head { margin-bottom: clamp(1.75rem, 4vw, 2.5rem); text-align: center; }
.block-title {
  margin: 0; font-size: clamp(1.5rem, 1.25rem + 1.1vw, 2.1rem);
  font-weight: var(--w-section); letter-spacing: var(--track-air); text-wrap: balance;
}
.block-intro { margin: 0.8rem auto 0; max-width: 56ch; color: var(--ink-3); }

/* 선은 음수 마진으로 겹친 1px 이다. 개수와 열수가 무엇이든 맞아 들어가고
   :nth-child 규칙이 필요 없다. 20rem 은 임의값이 아니다 — 71rem 안에서 열이
   3개가 되고, 특징을 여섯 개로 쓰면 두 줄이 정확히 찬다. */
.lattice {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
}
.cell {
  position: relative; isolation: isolate;
  padding: clamp(1.25rem, 3vw, 1.75rem) clamp(1rem, 2vw, 1.5rem);
  min-width: 0;
  margin: 0 -1px -1px 0;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
/* hover 에서 위로 올린다. 그러지 않으면 이웃에 가려 두 변만 밝아진다. */
.cell:hover { z-index: 1; border-color: var(--rule-2); }
.cell { transition: border-color var(--tr-ink); }

.cell-index {
  display: block; font-family: var(--mono); font-size: 0.66rem;
  letter-spacing: 0.18em; color: var(--ink-4); margin-bottom: 0.7rem;
  transition: color var(--tr-ink);
}
.cell:hover .cell-index { color: var(--accent); }
.cell-title { margin: 0; font-size: 1rem; font-weight: var(--w-label); letter-spacing: var(--track-set); }
.cell-desc { margin: 0.5rem 0 0; font-size: 0.86rem; color: var(--ink-3); text-wrap: pretty; }

/* ---------- terminal ---------- */

.term {
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  overflow: hidden; background: var(--bg-recess);
  max-width: 52rem; margin-inline: auto;
}
.term-bar {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.55rem 0.85rem;
  border-bottom: 1px solid var(--rule); background: var(--bg-2);
}
.term-dots { display: inline-flex; gap: 0.32rem; }
.term-dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--rule-3); }
.term-dots i:first-child { background: var(--ink-4); }
.term-name { font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.1em; color: var(--ink-4); }

.term-body {
  margin: 0; padding: 1.2rem 1.3rem; overflow-x: auto;
  font-family: var(--mono); font-size: 0.8rem; line-height: 1.85; white-space: pre;
}
.term-body code { font: inherit; background: none; padding: 0; border: 0; }
.term-body span { display: block; }
.term-cmd { color: var(--ink); }
/* 주석은 기울이고, 출력은 중립 회색이다. 출력에 색을 주면 그게 결과가 아니라
   상태로 읽힌다. */
.term-comment { color: var(--ink-4); font-style: italic; }
.term-out { color: var(--ink-3); }
.term-caption {
  margin: 0.8rem auto 0; max-width: 52rem; text-align: center;
  font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.06em; color: var(--ink-4);
}

/* ---------- cta ---------- */

.cta { margin-top: clamp(3rem, 7vw, 5rem); padding-block: clamp(2.5rem, 6vw, 5rem); text-align: center; }
.cta-inner { max-width: 46ch; margin-inline: auto; }
.cta-title {
  margin: 0; font-size: clamp(1.6rem, 1.3rem + 1.4vw, 2.4rem);
  font-weight: var(--w-display); letter-spacing: var(--track-air); text-wrap: balance;
}
.cta-text { margin: 0.9rem 0 0; color: var(--ink-3); }

/* ---------- about ---------- */

.about-head { text-align: center; }
.portrait { margin: 0 auto clamp(1.5rem, 4vw, 2.25rem); width: min(100%, 12rem); }
.portrait img {
  display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
  border: 1px solid var(--rule-2); border-radius: var(--radius);
}

/* ---------- toc & pager ---------- */

.toc { margin: 2rem auto 2.5rem; max-width: var(--read); padding: 1rem 1.25rem; border-left: 1px solid var(--rule-2); }
.toc-label {
  margin: 0 0 0.5rem; font-size: 0.66rem;
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-4);
}
.toc ul { list-style: none; margin: 0; padding: 0; }
.toc ul ul { padding-left: 1rem; }
.toc li { margin: 0.3rem 0; font-size: 0.88rem; }
.toc a { color: var(--ink-3); transition: color var(--tr-ink); }
.toc a:hover { color: var(--accent); }

/* ---------- reading rail ---------- */

/* 목차의 두 번째 모습. 넓은 화면에서 오른쪽 가장자리에 서고, 절마다 세로
   헤어라인 한 칸이며 칸의 높이가 그 절이 글에서 차지하는 몫이다. 목록은 절이
   몇 개인지 말하고 지도는 어느 절이 긴지 말한다.

   레일이 실제로 만들어졌을 때만 목록이 물러난다(.has-rail 은 ui.js 가 붙인다).
   스크립트가 못 오면 목록이 그대로 남고, 그게 이 장치의 폴백이다.

   처음 판은 본문 칼럼에 붙여 놓고 눈금 자체만 손을 받게 했다. 재 보니 레일이
   차지한 넓이의 14% 만 반응했고, 눈금에서 이름으로 커서를 옮기면 이름이
   대부분 사라졌다 — 이름과 세로로 겹치는 28% 구간에서만 건너갈 수 있었다.
   가끔 되는 조작은 안 되는 조작보다 나쁘다. 지금은 여백 전체가 손을 받는다. */

.rail { display: none; }

/* 이 시트의 첫 min-width 다. 나머지는 전부 max-width — 넓게 시작해서 좁아지면
   뺀다는 자세이고, 이것만 예외인 이유는 레일이 없던 자리를 요구하는 유일한
   요소이기 때문이다. */
@media (min-width: 80rem) {
  .rail {
    display: grid;
    /* 이름이 안쪽, 눈금이 바깥쪽이다. 눈금은 스크롤 막대 옆에 서는 스크롤
       가구이고 이름은 글 쪽으로 흐른다 — 반대로 두면 눈이 글에서 멀어지는
       방향으로 읽어 나가야 한다. */
    grid-template-columns: minmax(0, 1fr) 2px;
    column-gap: 0.9rem;
    row-gap: 0.3rem;
    /* 행 높이는 ui.js 가 grid-template-rows 로 쓴다. 겹침 풀이를 격자에 맡기면
       참조 구현이 손으로 짠 두 번의 밀어내기 패스가 통째로 없어지고, 이름이
       엉뚱한 눈금 옆에 가는 일이 구조적으로 불가능해진다. */

    position: fixed;
    /* 창 가장자리에 붙는다. 본문 칼럼에 매달았더니 1920px 에서 레일이 창 끝에서
       551px 떨어진 자리에 섰다 — 손이 가는 자리가 아니다. 스크롤 막대를 향해
       뻗는 손이 지나가는 띠가 이 화면에서 가장 자주 밟히는 빈 자리이고,
       여기 두면 찾으려 하지 않아도 걸린다.
       폭은 본문과 겹치지 않는 만큼까지만 — 1280px 에서 28px 이 남는다. */
    right: 0;
    width: min(16rem, calc(50vw - var(--read) / 2 + var(--gutter) - 1.5rem));
    /* 눈금과 창 끝 사이. 스크롤 막대 바로 옆이 이 물건의 자리다 — 멀어질수록
       본문 여백에 떠 있는 장식으로 보이고, 붙을수록 스크롤 가구로 읽힌다.
       포커스 링이 2px 테두리에 3px 오프셋이라 그만큼은 남긴다. */
    padding-right: 0.3rem;
    top: 50%; transform: translateY(-50%);
    height: min(64vh, 34rem);
    /* 마스트헤드(30) 아래. 올라온 이름이 크롬을 덮으면 안 된다. */
    z-index: 20;
  }

  /* 눈금은 세 단의 밝기다: 아직 안 읽은 것, 읽은 것, 지금 읽는 것. 색조는 한
     가지뿐이라 2px 에서도 순서가 읽힌다. 한때 읽은 자리를 잉크로, 지금 자리를
     주황으로 칠한 적이 있는데 그때는 과거가 현재보다 두 배 진했다 — 두 색을
     섞으면 어느 쪽이 앞인지 정할 방법이 없다. 색은 하나고, 순서는 길이가 짓는다. */
  /* 아직 안 읽은 자리. 중립 헤어라인이라 주황이 지나간 자리와 안 지나간 자리가
     색조로 갈린다. */
  .rail-seg { background: var(--rule-3); }

  /* 읽은 만큼. 이 사이트의 유일한 색이고, 여기가 그 색이 상시로 서는 유일한
     자리다 — 다른 데서는 손이 닿을 때만 나온다. 진행은 한 가지 색이 단조롭게
     자라는 것으로만 말한다: 위는 다 차고, 지금 절만 덜 찼고, 아래는 비어 있다.
     "지금"을 다른 색으로 칠하지 않는 이유가 그거다. 덜 찬 칸이 이 열에 하나뿐이라
     색을 더 쓰면 읽는 사람이 순서를 매길 수 없는 두 번째 변수가 생긴다.

     라이트에서 이 주황은 바탕 대비 2.12:1 이다. 낮다 — 그래서 이 레일은 보조
     표시이고, 실제 목차는 좁은 화면과 스크립트가 없을 때 그대로 나오는 본문의
     목록이 진다. 이름의 현재 표시는 색이 아니라 aria-current 와 잉크가 진다.

     높이는 ui.js 가 스크롤에서 직접 쓴다 — 전환을 걸면 화면보다 한 박자 늦게
     따라와 거짓말을 한다. 이미 연속값에 매인 것은 애니메이션하지 않는다. */
  .rail-fill {
    display: block; width: 100%; height: 0;
    background: var(--accent);
  }

  .rail-label {
    align-self: start;
    /* 오른쪽 정렬이라 이름들이 눈금에 붙어 한 덩이로 읽힌다. 왼쪽 정렬로 두면
       길이가 제각각인 세 구절이 빈 자리에 흩어져 목록으로 안 보인다. */
    text-align: right;
    /* 두 줄까지. 칸의 아래끝 2.2rem 이 정확히 두 줄 높이라, 넘치면 옆 칸을
       침범한다. -webkit-line-clamp 를 쓰지 않는 이유는 카드 제목과 같다 — 이
       조합에서 display 가 flow-root 로 계산돼 clamp 가 죽고 overflow 만 남으면,
       상자가 line-height 만큼이라 글자 줄상자(1.44em)의 아래가 잘린다.
       ponytail: 여기 걸리는 제목이 잦아지면 breakpoint 를 84rem 으로 올려 이름
       열을 넓히는 쪽이 먼저다. 글자 크기를 줄이는 건 그다음. */
    max-height: calc(2 * 1.35em + 0.12em);
    padding-bottom: 0.12em;
    overflow: hidden;
    font-size: 0.72rem; line-height: 1.35;
    /* 13.7px 글자다. --ink-3 은 라이트에서 4.23:1 로 AA 아래이고, 여기서 쓰던
       hover 액센트는 2.12:1 이라 손을 얹으면 더 안 읽혔다 — 이 열에서 액센트가
       두 번째로 틀린 자리다. 밝기로만 간다. */
    color: var(--ink-2);
    opacity: 0; pointer-events: none;
    transition: opacity var(--tr-ink), color var(--tr-ink);
  }
  .rail-label[aria-current] { color: var(--ink); }

  /* 드러나는 것은 숨어 있던 것이지 자리를 옮긴 것이 아니다. 이름은 제자리에서
     밝아진다 — 굵어지지도, 밀려나지도 않는다. 여백 전체가 방아쇠라, 한 번 나온
     이름까지 커서가 가는 동안 사라지지 않는다. 박자는 이 시트의 유일한 박자다.

     눈금은 hover 에 반응하지 않는다. --rule-3 에서 --ink-4 로 옮겼더니 라이트에서
     1.78:1 → 1.72:1 로 오히려 흐려졌고 다크에서는 1.76 → 1.77 로 아무 일도
     일어나지 않았다 — 두 토큰의 밝기가 두 테마 모두에서 거의 같다. 사이에 낄
     단이 없고, 이름 넷이 한꺼번에 나타나는 것이 이미 충분한 대답이다.
     좁아지면 줄이는 게 아니라 뺀다. */
  .rail:hover .rail-label,
  .rail:focus-within .rail-label { opacity: 1; pointer-events: auto; }

  /* 손이 얹힌 이름은 밝아진다. 지금 절과 같은 잉크가 되지만 둘을 가르는 것은
     색이 아니라 눈금이다 — 지금 절만 덜 찬 칸을 갖는다. 내비도 같은 규칙이다. */
  .rail-label:hover { color: var(--ink); }

  .has-rail .toc { display: none; }
}

/* ---------- prose ---------- */

.prose { font-size: 1.02rem; line-height: 1.78; }
.prose-intro { margin-bottom: 2.5rem; color: var(--ink-2); text-align: center; }
.project .prose, .prose-intro { max-width: var(--read); margin-inline: auto; }

/* 여백은 형제 사이에만. 타입 셀렉터로 초기화하면 특정성이 높아 아래 규칙을
   이기고 문단 사이 여백이 통째로 사라진다. */
.prose > * { margin: 0; }
.prose > * + * { margin-top: 1.2rem; }
.prose p { text-wrap: pretty; }

.prose h2, .prose h3, .prose h4 {
  margin: 2.75rem 0 0; line-height: 1.25; letter-spacing: 0.012em;
  text-wrap: balance; scroll-margin-top: calc(var(--masthead-h) + 1rem);
}
.prose h2 { font-size: clamp(1.3rem, 1.15rem + 0.7vw, 1.6rem); font-weight: var(--w-section); }
.prose h3 { font-size: 1.12rem; font-weight: var(--w-item); }
.prose h4 { font-size: 1rem; font-weight: var(--w-label); }
.prose h2 + *, .prose h3 + *, .prose h4 + * { margin-top: 0.8rem; }

.prose a { color: var(--ink); text-decoration: underline; text-underline-offset: 0.2em; text-decoration-color: var(--rule-3); }
.prose a:hover { text-decoration-color: var(--accent); }

.prose ol { margin-top: 1.2rem; padding-left: 1.35rem; }
.prose ul { margin-top: 1.2rem; list-style: none; padding-left: 1.1rem; }
.prose li { margin: 0.4rem 0; }
.prose ul > li { position: relative; }
.prose ul > li::before {
  content: ""; position: absolute; left: -1.1rem; top: 0.85em;
  width: 0.45rem; height: 1px; background: var(--ink-4);
}

.prose blockquote {
  margin-top: 1.8rem; padding-left: 1.25rem;
  border-left: 1px solid var(--rule-3); color: var(--ink-2);
}

.prose code {
  font-family: var(--mono); font-size: 0.86em;
  padding: 0.12em 0.35em; border-radius: var(--radius); background: var(--bg-3);
}
.prose pre {
  position: relative; margin-top: 1.5rem; padding: 1.1rem 1.2rem; overflow-x: auto;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--bg-recess); font-size: 0.82rem; line-height: 1.75;
}
.prose pre code { padding: 0; background: none; font-size: inherit; }

.copy-btn {
  position: absolute; top: 0.5rem; right: 0.5rem;
  padding: 0.18rem 0.5rem;
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  background: var(--bg-2); color: var(--ink-4);
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.1em;
  text-transform: uppercase; cursor: pointer; opacity: 0;
  transition: opacity var(--tr-ink), color var(--tr-ink), border-color var(--tr-ink);
}
.prose pre:hover .copy-btn, .prose pre:focus-within .copy-btn { opacity: 1; }
.copy-btn:hover { color: var(--ink); border-color: var(--rule-3); }
.copy-btn.is-done { opacity: 1; color: var(--accent); border-color: var(--accent); }

.prose table {
  width: 100%; margin-top: 1.75rem; border-collapse: collapse;
  font-size: 0.9rem; font-variant-numeric: tabular-nums;
}
.prose th, .prose td { padding: 0.55rem 0.75rem; text-align: left; border-bottom: 1px solid var(--rule); }
.prose th {
  font-size: 0.64rem; font-weight: 500;
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-4);
  border-bottom-color: var(--rule-2);
}
.prose td { color: var(--ink-2); }
.prose hr { margin-top: 2.5rem; border: 0; border-top: 1px solid var(--rule); }

.prose .anchor { opacity: 0; color: var(--ink-4); text-decoration: none; transition: opacity var(--tr-ink); }
.prose h2:hover .anchor, .prose h3:hover .anchor, .prose h4:hover .anchor,
.prose .anchor:focus-visible { opacity: 1; }

/* ---------- search palette ---------- */

.palette {
  width: min(38rem, calc(100vw - 2rem)); margin-top: 12vh; padding: 0;
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  background: var(--bg-2); color: var(--ink);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
}
.palette::backdrop { background: rgba(1, 1, 2, 0.66); backdrop-filter: blur(4px); }
.palette-form { margin: 0; }
.palette-head {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.85rem 1rem; border-bottom: 1px solid var(--rule); color: var(--ink-4);
}
.palette-input {
  flex: 1; min-width: 0; border: 0; background: none; color: var(--ink);
  font: inherit; font-size: 0.98rem; outline: none;
}
.palette-close { border: 0; background: none; cursor: pointer; padding: 0; color: var(--ink-4); }
.palette-close kbd { font-family: var(--mono); font-size: 0.64rem; }
.palette-results { list-style: none; margin: 0; padding: 0.4rem; max-height: 56vh; overflow-y: auto; }
.palette-results:empty { display: none; }
.palette-results a { display: block; padding: 0.6rem 0.7rem; border-radius: var(--radius); }
.palette-results li[aria-selected="true"] a, .palette-results a:hover { background: var(--bg-3); }
.palette-title { display: block; font-size: 0.94rem; font-weight: 400; }
.palette-crumb {
  display: block; margin-top: 0.15rem;
  font-size: 0.66rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-4);
}
.palette-results mark { background: none; color: var(--accent); }
.palette-status {
  margin: 0; padding: 0.55rem 1rem; border-top: 1px solid var(--rule);
  font-family: var(--mono); font-size: 0.66rem; color: var(--ink-4);
}
.palette-status:empty { display: none; }

/* ---------- colophon ---------- */

/* 여백은 세 줄을 서로 떼어 놓을 만큼만. 크롬은 읽는 물건이 아니라 나가는
   길이라, 본문만 한 자리를 차지하면 페이지가 거기서 한 번 더 시작하는 것처럼
   보인다. */
.colophon { border-top: 1px solid var(--rule); padding-block: 2rem 2.5rem; }
.colophon-in {
  max-width: var(--wide); margin-inline: auto; padding-inline: var(--gutter);
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 0.9rem 2rem; text-align: center;
}
.colophon-nav { display: flex; flex-wrap: wrap; justify-content: center; gap: 1.75rem; }
.colophon-nav a {
  font-size: 0.66rem; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-4); transition: color var(--tr-ink);
}
.colophon-nav a:hover { color: var(--ink-2); }
/* 갈래 링크와 소셜은 다른 줄에 선다 — 안을 도는 것과 밖으로 나가는 것이다. */
.colophon-social { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem; flex-basis: 100%; }
/* 아이콘만 남긴다. GitHub 와 Instagram 은 마크만으로 읽히는 몇 안 되는 표지라
   낱말이 하는 일이 없다 — 이름은 aria-label 이 진다. */
.colophon-social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem;
  font-size: 1rem; color: var(--ink-3);
  transition: color var(--tr-ink);
}
.colophon-social a:hover { color: var(--accent); }
.colophon-social svg { flex: none; }

.colophon-note { margin: 0; flex-basis: 100%; font-size: 0.7rem; letter-spacing: 0; color: var(--ink-4); }
.colophon-note a { color: var(--ink-3); transition: color var(--tr-ink); }
.colophon-note a:hover { color: var(--accent); }

/* ---------- Korean ---------- */

/* 한국어는 라틴 타이포 장치 셋을 깨뜨린다. letter-spacing 은 음절 블록을 벌리고,
   text-transform 은 아무 일도 하지 않으며, monospace 는 한글이 프로포셔널로
   폴백하는데 공백만 mono 폭이라 어절 사이가 두 배로 벌어진다.

   여기 걸리는 것은 **한국어가 들어가는 자리만**이다. 크롬(내비·푸터·컨트롤·
   문 링크)은 두 언어에서 영어로 고정이므로 대문자와 자간을 그대로 둔다 —
   되돌리면 한국어 페이지에서만 라벨이 소문자로 나와 두 언어가 달라 보인다. */
html[lang="ko"] .eyebrow,
html[lang="ko"] .fact-label,
html[lang="ko"] .prose th,
html[lang="ko"] .crumbs,
html[lang="ko"] .palette-crumb,
html[lang="ko"] .btn,
html[lang="ko"] .back,
html[lang="ko"] .badges li {
  font-family: var(--sans);
  letter-spacing: 0;
  text-transform: none;
}

/* 초경량은 한글에서도 나오지만 아주 큰 크기에서만 버틴다. 한글은 같은 굵기에서
   획이 훨씬 많아 라틴보다 먼저 뭉개지고, 작아지면 획이 끊어져 보인다.
   사다리 전체를 한 단씩 올린다 — 선택자마다 예외를 적으면 라틴 쪽 사다리를
   고칠 때 이쪽이 조용히 어긋난다. */
html[lang="ko"] {
  --w-display: 300;
  --w-section: 350;
  --w-item:    400;
  --w-label:   500;
  /* 자간도 여기서 끈다. letter-spacing 은 한글에서 음절 블록 사이를 벌려
     한 낱말이 여러 낱말로 보이게 만든다 — 라틴에서 숨을 주는 그 값이
     한글에서는 정확히 반대로 작동한다. */
  --track-air: 0;
  --track-set: 0;
}

/* 숫자와 날짜는 한국어에서도 mono 다. 숫자에는 위 문제가 없고 자릿수가 맞아야
   목록이 줄을 이룬다. */
html[lang="ko"] .ledger-date,
html[lang="ko"] .feature-date,
html[lang="ko"] .ledger-year,
html[lang="ko"] .term-body,
html[lang="ko"] .stars { font-family: var(--mono); }

/* ---------- reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* 전환만 끄면 변형된 자리에 멈춘다. 종점도 같이 중화한다. */
  .js .hero-home [data-rise] { animation: none; opacity: 1; transform: none; }
  .frame:hover .frame-media,
  .frame:hover .frame-media img,
  .tile-links a:hover { transform: none; }
}
