static/css/main.css (71313B)
1 /* sqzass 테마 — marigold
2 *
3 * 이 사이트가 쓰는 테마이자 sqzass 로 만든 첫 테마다. 이름은 이 시트의 유일한
4 * 색에서 따왔다. 두 번째 테마가 생기면 그것도 자기 색으로 불리면 된다.
5 *
6 * 차가운 니어블랙 위의 가운데 정렬 갤러리. 색은 평상시에 없고 손이 닿을 때만
7 * 나온다 — 액센트가 브랜딩이 아니라 보상이다.
8 *
9 * hover 는 색만 바꾼다. 크기가 움직이면 시선이 그리로 끌려가는데, 이 사이트에서
10 * 커서가 지나가는 자리는 대부분 읽는 중인 자리다.
11 *
12 * 목소리는 굵기로 만든다. 디스플레이는 230~350 의 초경량에 **양수** 트래킹이고,
13 * 라벨은 작고 넓게 벌어진 대문자다. 큰 글자는 가늘고 작은 글자는 벌어진다.
14 * 굵은 글씨는 이 사이트에 없다.
15 *
16 * 서체는 Noto Sans KR 가변 한 벌이다. 라틴과 한글을 unicode-range 로 갈라
17 * 두어서 영문 페이지는 한글 파일을 받지 않는다. 서브셋은 tools/fonts.py 가
18 * 만들고, 빠진 글자는 CI 가 빌드 전에 잡는다.
19 *
20 * 테마는 [data-theme] 이 정한다. 생성된 하이라이트 스타일시트가 같은 속성을
21 * 보기 때문에, prefers-color-scheme 에 맡기면 둘이 어긋날 수 있다.
22 */
23
24 @font-face {
25 font-family: "Display";
26 src: url("/fonts/display-latin.woff2") format("woff2");
27 font-weight: 100 900;
28 font-display: swap;
29 unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20A0-20BF,
30 U+2122, U+2190-21BB, U+2212, U+2500-257F, U+25A0-25FF, U+2605-2606;
31 }
32 @font-face {
33 font-family: "Display";
34 src: url("/fonts/display-kr.woff2") format("woff2");
35 font-weight: 100 900;
36 font-display: swap;
37 unicode-range: U+1100-11FF, U+3130-318F, U+AC00-D7A3, U+FF01-FF60;
38 }
39 @font-face {
40 font-family: "JetBrains Mono";
41 src: url("/fonts/jetbrains-mono-400.woff2") format("woff2");
42 font-weight: 400;
43 font-display: swap;
44 }
45 @font-face {
46 font-family: "JetBrains Mono";
47 src: url("/fonts/jetbrains-mono-700.woff2") format("woff2");
48 font-weight: 700;
49 font-display: swap;
50 }
51
52 /* ---------- tokens ---------- */
53
54 :root {
55 color-scheme: dark;
56
57 /* 바닥에 색이 있다. 이 집 사이트들은 전부 그렇다 — ommp 는 청보라(#0A051E,
58 앱의 BG_DARK 를 그대로), blog 는 청록, sqzass 문서는 청흑, 랜딩은 웜브라운.
59 순수 중립 회색은 아무 말도 하지 않는다. */
60 --bg-recess: #030405;
61 --bg: #07090a;
62 --bg-2: #0e1112;
63 --bg-3: #151819;
64
65 --ink: #eaeeec;
66 --ink-2: #9ba39f;
67 --ink-3: #737b77;
68 --ink-4: #363c39;
69
70 --rule: #16191a;
71 --rule-2: #232728;
72 --rule-3: #363b3c;
73
74 /* 색이 아니라 삼원소. 토큰 하나로 alpha 0.03~0.75 를 전부 낸다 —
75 hex 였다면 매번 color-mix 를 거쳐야 했을 값들이다. */
76 --ink-rgb: 234, 238, 236;
77
78 /* 이 테마의 이름이자 유일한 색. 평상시에는 어디에도 없고 링크·포커스 링·
79 현재 항목·손이 닿은 것에만 나온다 — 주황이면 갈 수 있는 것이거나 지금
80 있는 자리다.
81 지어낸 값이 아니라 About 페이지 초상의 배경에서 그대로 뽑았다
82 (tools/icons.py 옆에서 샘플링, hsl 36° 75%). ommp 가 모든 색을 앱 소스에서
83 가져오는 것과 같은 규칙이고, 차가운 바닥과는 보색이라 손이 닿는 자리가
84 확실히 뜬다. 니어블랙 대비 9.07:1. */
85 --accent: #e4a242;
86 /* 액센트 위에 얹히는 잉크. 액센트가 밝은 주황이라 두 테마 모두 어둡다 —
87 ::selection 처럼 액센트가 배경이 되는 자리에서 --bg 를 쓰면 라이트에서
88 흰 글자가 주황 위에 놓여 읽히지 않는다. */
89 --on-accent: #14140f;
90
91 /* 선화 마크는 순수 흑 아니면 순수 백이다. 중간 회색은 비활성으로 읽힌다. */
92 --mark-ink: #ffffff;
93
94 --sans: "Display", -apple-system, BlinkMacSystemFont, "Segoe UI",
95 "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", sans-serif;
96 --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
97
98 /* 굵기 사다리. 네 단이고 간격이 고르다 — 선택자마다 값을 적으면 사다리가
99 아니라 우연의 모음이 된다. 굵은 글씨는 여전히 없다: 제일 무거운 단이 420 이다.
100 트래킹은 둘뿐이다. 가는 글자를 크게 쓰면 자간이 필요하고, 두꺼워질수록
101 그 필요가 줄어든다. */
102 --w-display: 180;
103 --w-section: 260;
104 --w-item: 340;
105 --w-label: 420;
106 --track-air: 0.03em;
107 --track-set: 0.014em;
108
109 --radius: 2px;
110 --masthead-h: 3.5rem; /* sqzass 문서와 같은 높이. 내용이 아니라 크롬이라 얇아야 한다 */
111 --gutter: clamp(1.25rem, 5vw, 3rem);
112 --wide: 71rem;
113 --read: 44rem;
114
115 /* 한 박자. 값을 여기 한 번만 두면 모든 상호작용이 같은 속도로 반응하고,
116 그게 "같은 물건"이라는 느낌을 만든다. hover 는 색만 바꾸므로 곡선도 하나면 된다. */
117 --ease: cubic-bezier(0.16, 1, 0.3, 1);
118 --tr-ink: 0.25s ease;
119 }
120
121 :root[data-theme="light"] {
122 color-scheme: light;
123
124 --bg-recess: #e9ecea;
125 --bg: #fafbfa;
126 --bg-2: #f2f4f3;
127 --bg-3: #eaeded;
128
129 --ink: #0e1211;
130 --ink-2: #515856;
131 --ink-3: #737a78;
132 --ink-4: #bdc3c1;
133
134 --rule: #e4e8e6;
135 --rule-2: #d5dad8;
136 --rule-3: #bac0be;
137
138 --ink-rgb: 14, 18, 17;
139 /* 액센트는 두 테마가 같다. 밝은 바닥에서 대비가 낮아지지만, 이 색은
140 평상시에 나오지 않고 hover·focus·현재 위치에만 쓰이며 그 자리에는 늘
141 상태 변화가 함께 있다(밑줄, 테두리, 눈금). 색 하나로만 뜻을 전하는
142 자리는 없다. */
143 --accent: #e4a242;
144 --mark-ink: #000000;
145 }
146
147 *, *::before, *::after { box-sizing: border-box; }
148
149 /* 루트가 유동이라 모든 rem 이 이미 반응형이다. 무릎은 286px 과 1143px 이다. */
150 html {
151 font-size: clamp(16px, 15px + 0.35vw, 19px);
152 -webkit-text-size-adjust: 100%;
153 scroll-behavior: smooth;
154 }
155
156 body {
157 margin: 0;
158 background: var(--bg);
159 color: var(--ink);
160 font-family: var(--sans);
161 /* 루트가 이미 유동이라(clamp 16→19px) 본문까지 vw 에 매달면 두 번 조정된다.
162 1100px 위에서는 어차피 최대값에 고정돼 있던 값이다. */
163 font-size: 1.0625rem;
164 line-height: 1.7;
165 /* 한국어는 어절 안에서 줄바꿈하지 않는다. */
166 word-break: keep-all;
167 overflow-wrap: anywhere;
168 -webkit-font-smoothing: antialiased;
169 text-rendering: optimizeLegibility;
170 }
171
172 img { max-width: 100%; height: auto; }
173 a { color: inherit; text-decoration: none; }
174
175 :focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
176 ::selection { background: var(--accent); color: var(--on-accent); }
177
178 * { scrollbar-width: thin; scrollbar-color: var(--rule-2) transparent; }
179 ::-webkit-scrollbar { width: 8px; height: 8px; }
180 ::-webkit-scrollbar-track { background: transparent; }
181 ::-webkit-scrollbar-thumb { background: var(--rule-2); border-radius: 999px; }
182 ::-webkit-scrollbar-thumb:hover { background: var(--ink-4); }
183
184
185 .skip {
186 position: absolute; left: -9999px; top: 0; z-index: 100;
187 padding: 0.7rem 1.1rem; background: var(--bg-2);
188 border: 1px solid var(--accent); border-radius: var(--radius);
189 }
190 .skip:focus { left: 0.5rem; top: 0.5rem; }
191
192 /* ---------- layout ---------- */
193
194 .wrap {
195 width: 100%;
196 max-width: var(--wide);
197 margin-inline: auto;
198 padding-inline: var(--gutter);
199 }
200 .wrap-read { max-width: var(--read); }
201 /* 읽는 폭에 날짜 거터가 나가 앉을 자리를 더한 값. 숫자를 따로 고르지 않고
202 --read 에서 유도하면 본문 폭을 바꿨을 때 원장이 따라온다.
203 (ch 는 컨테이너에 쓰면 본문 글꼴 기준이라 절반밖에 안 나온다 — 한 번 틀렸다.) */
204 .wrap-ledger { max-width: calc(var(--read) + 12rem); }
205
206 main { display: block; padding-bottom: clamp(5rem, 12vw, 9rem); }
207
208 /* ---------- masthead ---------- */
209
210 .masthead {
211 position: sticky; top: 0; z-index: 30;
212 height: var(--masthead-h);
213 background: color-mix(in srgb, var(--bg) 82%, transparent);
214 backdrop-filter: blur(14px) saturate(1.5);
215 border-bottom: 1px solid var(--rule);
216 }
217
218 /* 3열 격자. 양옆 열이 1fr 로 같으므로 가운데 열이 화면 축에 정확히 앉는다.
219 flex 에 margin-inline:auto 를 쓰면 브랜드와 도구 덩이의 폭 차이만큼 내비가
220 밀리는데, 페이지 전체가 가운데축인 사이트에서는 그 어긋남이 보인다. */
221 .masthead-in {
222 display: grid;
223 grid-template-columns: 1fr auto 1fr;
224 align-items: center; gap: clamp(0.75rem, 3vw, 2rem);
225 height: 100%; max-width: var(--wide); margin-inline: auto;
226 padding-inline: var(--gutter);
227 }
228
229 .brand { display: inline-flex; align-items: center; gap: 0.55rem; flex: none; }
230
231 /* 마크는 잉크로 그린다. 원본이 흰 배경의 검은 선화라 <img> 로는 다크에서
232 사라지는데, 마스크로 두면 배경색이 곧 선 색이 된다. 흑 아니면 백. */
233 .brand-mark {
234 width: 1.9em; aspect-ratio: 128 / 123; flex: none;
235 background-color: var(--mark-ink);
236 -webkit-mask: url("/images/mark.png") center / contain no-repeat;
237 mask: url("/images/mark.png") center / contain no-repeat;
238 }
239 @supports not (mask-image: url("")) { .brand-mark { display: none; } }
240
241 .brand-name {
242 font-size: 0.82rem; font-weight: 500;
243 letter-spacing: 0.22em; text-transform: uppercase;
244 color: var(--ink);
245 }
246
247 .nav {
248 display: flex; align-items: center; justify-content: center;
249 gap: clamp(1rem, 2.5vw, 2rem);
250 min-width: 0; overflow-x: auto; scrollbar-width: none;
251 }
252 .nav::-webkit-scrollbar { display: none; }
253
254 /* 라벨은 작고 넓게 벌어진 대문자다. 상자도 배경도 없다. */
255 .nav a {
256 position: relative; flex: none; padding-block: 0.4rem;
257 font-size: 0.72rem; font-weight: 400;
258 letter-spacing: 0.18em; text-transform: uppercase;
259 color: var(--ink-3);
260 transition: color var(--tr-ink);
261 }
262 .nav a:hover { color: var(--ink); }
263 .nav a[aria-current] { color: var(--ink); }
264 /* 현재 위치는 1px 짧은 눈금. 밑줄은 링크의 표시이고 이건 상태의 표시다. */
265 .nav a[aria-current]::after {
266 content: ""; position: absolute; left: 50%; bottom: -0.15rem;
267 width: 1.4rem; height: 1px; margin-left: -0.7rem;
268 background: var(--accent);
269 }
270
271 .tools { display: flex; align-items: center; justify-content: flex-end; gap: 0.9rem; }
272
273 /* 크롬 컨트롤에는 테두리가 없다. 상자를 두르면 편집물이 아니라 대시보드가 된다. */
274 .chip {
275 display: inline-flex; align-items: center; gap: 0.4rem;
276 padding: 0; border: 0; background: none; cursor: pointer;
277 font: inherit; font-size: 0.7rem; letter-spacing: 0.12em;
278 text-transform: uppercase; color: var(--ink-3);
279 /* 컨트롤 라벨은 낱말 하나이고, 낱말 하나는 줄바꿈하지 않는다. body 가
280 overflow-wrap: anywhere 라 이걸 빼면 좁은 화면에서 "Search" 가 쪼개져
281 마스트헤드 높이를 밀어 올린다. */
282 white-space: nowrap;
283 transition: color var(--tr-ink);
284 }
285 .chip:hover { color: var(--ink); }
286
287 /* 키를 말하는 컨트롤 하나만 진짜 키캡이 된다 — 터미널에서 모는 도구라 얇은
288 테두리에 작은 반경. 네 칩에 전부 테두리를 두르면 테두리 없는 타일·원장·
289 액자와 싸워서 마스트헤드가 툴바가 된다. */
290 .chip kbd {
291 font-family: var(--mono); font-size: 0.66rem; line-height: 1; letter-spacing: 0;
292 padding: 0.18rem 0.36rem;
293 border: 1px solid var(--rule-2); border-radius: 3px;
294 color: var(--ink-4);
295 transition: border-color var(--tr-ink), color var(--tr-ink);
296 }
297 .chip:hover kbd { border-color: var(--rule-3); color: var(--ink-3); }
298 /* 라벨이 `Ctrl /` 에서 `⌘ /` 로 바뀌어도 폭이 안 변한다 — 누르려던 것이
299 도망가면 안 된다. */
300 .chip-search { min-width: 4.2rem; justify-content: flex-end; }
301
302 .i-moon { display: none; }
303 :root[data-theme="light"] .i-sun { display: none; }
304 :root[data-theme="light"] .i-moon { display: inline; }
305
306 .chip-word { display: none; }
307
308 @media (max-width: 46rem) {
309 .brand-name { display: none; }
310 /* 키캡은 키보드가 있을 때의 안내다. 없는 화면에서는 낱말이 대신 선다. */
311 .chip kbd { display: none; }
312 .chip-word { display: inline; }
313 .chip-search { min-width: 0; }
314 /* 가운데 축을 포기한다. 양옆을 1fr 로 묶어 두면 넓은 쪽(도구 열)의 폭이
315 좁은 쪽에도 강제되고, 그 낭비만큼 내비가 잘려 나간다. 축보다 다 보이는
316 게 낫다 — 축은 읽는 자리를 정하지만 잘린 라벨은 길을 지운다. */
317 .masthead-in { grid-template-columns: auto minmax(0, 1fr) auto; gap: 0.9rem; }
318 .nav { gap: 1rem; }
319 }
320
321 /* 손안의 화면에서는 마스트헤드에 들어갈 자리가 모자란다. 나가는 것은 GitHub 다 —
322 콜로폰에 같은 링크가 있고, 여기 남는 넷(갈래 셋과 검색)은 이 사이트 안을
323 도는 데 필요한 전부다. 잘린 라벨을 남기느니 중복을 하나 뺀다. */
324 @media (max-width: 34rem) {
325 .chip-github { display: none; }
326 .masthead-in { gap: 0.75rem; }
327 /* 여기서부터 내비는 밀어서 본다. 가운데 정렬로 두면 넘칠 때 왼쪽으로 흘러나간
328 부분에 손이 닿지 않는다 — 스크롤은 오른쪽으로만 되기 때문이다. */
329 .nav { gap: 0.85rem; justify-content: flex-start; }
330 }
331
332 /* ---------- page head: 가운데 축 ---------- */
333
334 .page-head {
335 padding-block: clamp(3.5rem, 9vw, 7rem) clamp(2rem, 4vw, 3.5rem);
336 text-align: center;
337 }
338
339 /* 프런트매터 펜스. 제목 블록이 끝나고 본문이 시작하는 자리에 한 페이지 한 번.
340 소스 파일에서 `+++` 가 표시하는 것과 같은 경계이고, 이 사이트의 모든 페이지가
341 실제로 `+++` 로 시작하는 sqzass 문서다. 장식처럼 보이는 유일한 것이며
342 장식이 아니다. (sqzass 문서 시트가 같은 장치를 같은 이유로 쓴다.) */
343 .fence {
344 font-family: var(--mono);
345 font-size: 1.0625rem;
346 letter-spacing: 0.1em;
347 color: var(--ink-4);
348 text-align: center;
349 margin: 0 0 clamp(2rem, 4vw, 2.75rem);
350 user-select: none;
351 }
352
353 .page-title {
354 margin: 0;
355 font-size: clamp(2rem, 1.4rem + 2.4vw, 3.2rem);
356 font-weight: var(--w-display);
357 letter-spacing: var(--track-air);
358 line-height: 1.14;
359 text-wrap: balance;
360 }
361
362 .page-lede {
363 margin: clamp(0.9rem, 2vw, 1.4rem) auto 0;
364 max-width: 52ch;
365 color: var(--ink-2);
366 text-wrap: pretty;
367 }
368
369 /* 표제 아래 한 줄. 날짜와 언어가 여기 함께 선다 — 둘 다 이 문서에 대한 사실이고,
370 따로 세우면 제목과 본문 사이에 줄만 하나 더 생긴다. 비어 있으면 gap 도 여백도
371 없다(빈 flex 컨테이너라 높이가 0 이다). */
372 .page-meta {
373 display: flex; flex-wrap: wrap; align-items: baseline;
374 justify-content: center; gap: 1.1rem;
375 margin: 1.2rem 0 0;
376 font-family: var(--mono); font-size: 0.74rem;
377 letter-spacing: 0.08em; font-variant-numeric: tabular-nums;
378 color: var(--ink-3);
379 }
380 .page-meta:empty { margin: 0; }
381
382 /* ---------- language switch ---------- */
383
384 /* 두 글자 코드 둘. 사이에 구분자를 두지 않는다 — 간격이 이미 나누고 있고,
385 가운뎃점이나 슬래시를 넣으면 두 낱말이 한 낱말처럼 붙어 보인다.
386
387 현재 언어가 제일 밝다. 크롬의 현재 항목이 밝아지는 것과 같은 규칙이고,
388 반대로 두면 "지금 어디에 있는지"를 알려고 두 회색을 견줘야 한다.
389 갈 수 있는 쪽에는 밑줄 자리가 비어 있다가 손이 닿으면 액센트로 채워진다 —
390 색만으로 뜻을 전하지 않는다는 이 사이트의 규칙 그대로다. */
391 /* 자기 크기를 스스로 갖는다. .page-meta 안에도 서고 프로젝트 히어로의 배지 줄
392 아래에도 서는데, 부모마다 크기가 달라지면 같은 컨트롤이 페이지마다 다른
393 물건으로 보인다. */
394 .langs {
395 display: inline-flex; gap: 0.55rem;
396 font-family: var(--mono); font-size: 0.7rem;
397 letter-spacing: 0.16em; text-transform: uppercase;
398 }
399 /* 히어로에서는 배지 줄 바로 아래 붙는다 — 둘 다 이 프로젝트에 대한 사실이다. */
400 .hero-main > .langs { margin-top: 0.85rem; }
401 .lang-cur { color: var(--ink); }
402 /* 갈 수 있는 쪽은 이 사이트의 링크 단이다(--ink-3). --ink-4 는 날짜와 펜스의
403 단이라 "있지만 누를 것은 아닌" 자리이고, 라이트 테마에서는 1.7:1 로 사실상
404 안 보인다. 지금 판(--ink)과의 차이는 이 단에서도 충분히 크다. */
405 .langs a {
406 color: var(--ink-3);
407 border-bottom: 1px solid transparent;
408 transition: color var(--tr-ink), border-color var(--tr-ink);
409 }
410 .langs a:hover { color: var(--ink-2); border-bottom-color: var(--accent); }
411
412 .eyebrow {
413 margin: 0 0 0.9rem;
414 font-size: 0.7rem; font-weight: 400;
415 letter-spacing: 0.22em; text-transform: uppercase;
416 color: var(--ink-3);
417 }
418
419 .crumbs { margin-bottom: 0.9rem; font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; }
420 .crumbs a { color: var(--ink-3); transition: color var(--tr-ink); }
421 .crumbs a:hover { color: var(--accent); }
422
423 /* ---------- wing title: 섹션 머리 ---------- */
424
425 /* 가운데 제목 양옆으로 짧은 헤어라인. 페이지를 연으로 끊는 장치이고,
426 위 여백이 넉넉해야 연이 연으로 읽힌다. */
427 /* 갈래 머리. 옆줄은 없다 — 한 가지 일에 한 가지 장치이고, 위아래 여백이
428 이미 연을 끊고 있다. 좁아지면 줄이는 게 아니라 뺀다. */
429 /* 갈래 머리는 한 줄이다: 이름, 그 갈래로 가는 문까지 이어지는 헤어라인, 그리고
430 문. 헤어라인은 장식이 아니라 둘을 한 줄의 양끝으로 묶는 것이고, 그래서 이름과
431 문 사이에 무엇이 오든 둘은 같은 줄에 있다.
432 아래 내용이 전부 왼쪽 정렬(카드의 라벨, 원장의 날짜 거터)이라 머리도 왼쪽에서
433 시작한다. 가운데축은 히어로와 글 한 편의 것이고, 갈래의 머리는 목록의 것이다. */
434 .room-head {
435 display: flex; align-items: center;
436 gap: clamp(0.9rem, 2vw, 1.5rem);
437 margin: clamp(4.5rem, 11vw, 7rem) 0 clamp(1.5rem, 3.5vw, 2.25rem);
438 }
439 .room-head h2 {
440 margin: 0; flex: none;
441 font-size: clamp(1.05rem, 0.95rem + 0.55vw, 1.3rem);
442 font-weight: var(--w-section); letter-spacing: var(--track-air); line-height: 1.2;
443 }
444 /* 남는 자리를 전부 먹는다. 문이 없는 갈래(프로젝트 묶음)에서는 끝까지 간다. */
445 .room-rule { flex: 1 1 auto; min-width: 1rem; height: 1px; background: var(--rule); }
446 .room-head-sub {
447 margin: -0.75rem 0 clamp(1.75rem, 4vw, 2.5rem);
448 max-width: 54ch;
449 font-size: 0.92rem; color: var(--ink-3);
450 }
451
452
453 /* ---------- home hero ---------- */
454
455 /* 첫 화면 전체를 쓴다. 스크롤 전에 사이트의 장르를 정하는 자리다. */
456 .hero-home {
457 min-height: calc(100svh - var(--masthead-h));
458 display: grid; place-items: center; text-align: center;
459 padding-block: clamp(2rem, 6vw, 4rem);
460 }
461 .hero-home > .wrap { display: grid; justify-items: center; }
462
463 .hero-mark {
464 width: clamp(3.4rem, 8vw, 5rem); aspect-ratio: 128 / 123;
465 background-color: var(--mark-ink);
466 -webkit-mask: url("/images/mark.png") center / contain no-repeat;
467 mask: url("/images/mark.png") center / contain no-repeat;
468 margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
469 }
470
471 .statement {
472 margin: 0; max-width: 20ch;
473 font-size: clamp(2.2rem, 1.3rem + 3.6vw, 4.2rem);
474 font-weight: var(--w-display); letter-spacing: var(--track-air); line-height: 1.1;
475 text-wrap: balance;
476 }
477
478 .statement-sub {
479 margin: clamp(1.2rem, 3vw, 1.8rem) auto 0;
480 max-width: 44ch; color: var(--ink-2); text-wrap: pretty;
481 }
482
483 /* 문. 채운 알약이 아니라 밑줄 그은 라벨이다. */
484 .door {
485 display: inline-block; margin-top: clamp(1.8rem, 4vw, 2.75rem);
486 padding-bottom: 0.35rem;
487 font-size: 0.72rem; letter-spacing: 0.22em; text-transform: uppercase;
488 color: var(--ink-2);
489 border-bottom: 1px solid var(--rule-3);
490 transition: color var(--tr-ink), border-color var(--tr-ink);
491 }
492 .door:hover { color: var(--ink); border-color: var(--accent); }
493
494 /* 페이지가 열릴 때 한 번. 스크롤에 반응해 숨었다 나타나지 않는다 —
495 읽던 자리로 돌아왔을 때 사라지는 것은 고장으로 보인다. */
496 @keyframes rise { from { opacity: 0; transform: translateY(0.75rem); } to { opacity: 1; transform: none; } }
497 .js .hero-home [data-rise] { animation: rise 0.9s var(--ease) both; }
498 .js .hero-home [data-rise="1"] { animation-delay: 0.12s; }
499 .js .hero-home [data-rise="2"] { animation-delay: 0.28s; }
500 .js .hero-home [data-rise="3"] { animation-delay: 0.42s; }
501
502 /* ---------- home rooms ---------- */
503
504 /* 홈의 갈래는 .wrap 보다 좁다. 1440 에서 1102px — 참조 카드(1123)와 같은 자리다.
505 머리도 카드도 타일도 이 폭을 쓰므로 셋의 왼쪽 끝과 오른쪽 끝이 같다. */
506 .room { max-width: calc(58rem + 2 * var(--gutter)); padding-block: 0; }
507
508 /* 갈래의 위 여백은 그 갈래의 첫 물건이 진다. 전에는 언제나 머리가 졌는데,
509 글 갈래에서는 머리가 카드 뒤로 갔다. */
510 .room > .feature:first-child { margin-top: clamp(4.5rem, 11vw, 7rem); }
511 /* 카드 뒤에 오는 머리는 갈래를 여는 것이 아니라 그 아래 목록을 여는 것이라
512 덜 띄운다. 갈래 사이의 거리와 갈래 안의 거리는 달라야 한다. */
513 .feature + .room-head { margin-top: clamp(2.5rem, 6vw, 3.5rem); }
514
515 /* 문은 머리의 오른쪽 끝에 선다. 밑줄은 이 사이트의 문(.door)과 같은 물건이다. */
516 .room-door {
517 flex: none;
518 padding-bottom: 0.25rem;
519 font-size: 0.7rem; letter-spacing: 0.22em; text-transform: uppercase;
520 color: var(--ink-3);
521 border-bottom: 1px solid var(--rule-2);
522 transition: color var(--tr-ink), border-color var(--tr-ink);
523 }
524 .room-door:hover { color: var(--ink); border-bottom-color: var(--accent); }
525
526 /* ---------- featured frames ---------- */
527
528 /* 액자는 테두리가 없다. 그림에만 테두리가 있고 캡션은 그 아래 떠 있다.
529 flex 라 셋이든 넷이든 마지막 줄에 하나만 남지 않는다. */
530 .frames {
531 display: flex; flex-wrap: wrap; justify-content: center;
532 gap: clamp(1.5rem, 3vw, 2.25rem);
533 }
534
535 .frame {
536 flex: 1 1 220px; max-width: 400px;
537 display: flex; flex-direction: column; align-items: center; text-align: center;
538 background: transparent; border: 0;
539 }
540
541 .frame-media {
542 width: 100%; aspect-ratio: 16 / 10;
543 border: 1px solid var(--rule-2); border-radius: var(--radius);
544 overflow: hidden; display: grid; place-items: center;
545 background: var(--bg-2);
546 transition: border-color var(--tr-ink);
547 }
548 .frame:hover .frame-media { border-color: var(--rule-3); }
549
550 /* 그림은 제 색으로 쉰다. 탈색해 두었다가 손이 닿으면 돌려주는 건 다른 집의
551 장치였다 — 여기서는 ommp 가 4K 컬러 스크린샷을 그대로 싣는 쪽이 기준이다. */
552 .frame-media img { width: 100%; height: 100%; object-fit: cover; }
553
554 .frame-mark {
555 width: 42%; aspect-ratio: 1;
556 background-color: var(--mark-ink);
557 -webkit-mask: var(--mark-src) center / contain no-repeat;
558 mask: var(--mark-src) center / contain no-repeat;
559 opacity: 0.72; transition: opacity var(--tr-ink);
560 }
561 .frame:hover .frame-mark { opacity: 1; }
562
563 .frame-title {
564 display: flex; align-items: center; justify-content: center; gap: 0.5rem;
565 margin: clamp(0.9rem, 2vw, 1.25rem) 0 0;
566 font-size: 1.25rem; font-weight: var(--w-item); letter-spacing: var(--track-set);
567 }
568 .frame-title a { transition: color var(--tr-ink); }
569 .frame:hover .frame-title a { color: var(--accent); }
570
571 /* 이름 옆 작은 마크. 크기를 em 으로 잡아 제목이 커지면 같이 커진다. */
572 .chip-mark {
573 width: 1.55em; height: 1.55em; flex: none;
574 display: grid; place-items: center;
575 border: 1px solid var(--rule-2); border-radius: var(--radius);
576 background: var(--bg-2);
577 }
578 .chip-mark::before {
579 content: ""; width: 74%; height: 74%;
580 background-color: var(--mark-ink);
581 -webkit-mask: var(--mark-src) center / contain no-repeat;
582 mask: var(--mark-src) center / contain no-repeat;
583 }
584 .chip-mark img { width: 74%; height: 74%; object-fit: contain; }
585
586 .frame-blurb {
587 margin: 0.6rem 0 0; max-width: 40ch;
588 font-size: 0.9rem; color: var(--ink-3); text-wrap: pretty;
589 }
590
591 .frame-meta {
592 display: flex; align-items: center; justify-content: center;
593 gap: 0.9rem; margin-top: 0.9rem;
594 }
595
596 /* ---------- no image ---------- */
597
598 /* 그림이 아직 없는 자리. 회색 상자로 때우지 않고 비었다고 말한다 —
599 나중에 무엇을 채워야 하는지가 화면에 보여야 한다. */
600 .noimg {
601 width: 100%; height: 100%;
602 display: grid; place-items: center;
603 background:
604 repeating-linear-gradient(45deg,
605 transparent, transparent 7px,
606 rgba(var(--ink-rgb), 0.022) 7px, rgba(var(--ink-rgb), 0.022) 14px);
607 }
608 .noimg::after {
609 content: "no image";
610 font-family: var(--mono); font-size: 0.62rem;
611 letter-spacing: 0.2em; text-transform: uppercase;
612 color: var(--ink-4);
613 border: 1px solid var(--rule-2); border-radius: var(--radius);
614 padding: 0.3rem 0.6rem;
615 }
616
617 /* ---------- project tiles ---------- */
618
619 /* 카드다. 테두리와 바닥을 갖고, 글은 왼쪽에서 시작한다.
620 테두리가 없던 때는 이 여섯 개가 페이지에서 유일하게 액자 없는 물건이라,
621 위의 카드 하나만 상자로 떠 있고 아래는 글자만 떠다녔다. 갈래 머리가 왼쪽에서
622 시작하는데 그 아래 내용이 가운데 정렬인 것도 같은 어긋남이었다. */
623 /* 트랙에 상한을 두고 justify-content 로 가운데 모은다. `1fr` 로 두면 항목이
624 두 개일 때 3열 격자의 왼쪽 두 칸에 붙어 페이지 축이 어긋난다 — 갈래마다
625 개수가 다른 목록에서는 이게 항상 일어난다. */
626 .tiles {
627 display: grid;
628 grid-template-columns: repeat(auto-fit, minmax(15rem, 21rem));
629 justify-content: center;
630 gap: clamp(0.5rem, 1.5vw, 1rem);
631 }
632
633 .tile {
634 position: relative; isolation: isolate;
635 display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start;
636 gap: 0.5rem; text-align: left;
637 padding: clamp(1.25rem, 3vw, 1.75rem);
638 border: 1px solid var(--rule); border-radius: var(--radius);
639 background: var(--bg-2);
640 transition: border-color var(--tr-ink);
641 }
642 /* 손이 닿으면 테두리가 한 단 오른다 — 맨 위 카드와 같은 응답이다. */
643 .tile:hover { border-color: var(--rule-2); }
644 /* 누르면 눌린다. 빛이 번지는 대신 물건이 압축된다 — 이 집의 이름들이 전부
645 모음을 눌러 뺀 것이고(sqzer, sqzass, ommp), 랜딩이 이미 같은 값을 쓴다.
646 0.988 은 눈이 알아채되 글자가 흐려지지는 않는 지점이다. */
647
648 .tile-title {
649 display: flex; align-items: center; gap: 0.45rem;
650 margin: 0; font-size: 1.05rem; font-weight: var(--w-item); letter-spacing: var(--track-set);
651 color: var(--ink-2); transition: color var(--tr-ink);
652 }
653 .tile:hover .tile-title { color: var(--ink); }
654 .tile-title .chip-mark { width: 1.4em; height: 1.4em; }
655
656 .tile-blurb {
657 margin: 0;
658 font-size: 0.85rem; line-height: 1.6; color: var(--ink-3); text-wrap: pretty;
659 }
660
661 /* 사실 줄은 카드 아래쪽에 붙는다. margin-top:auto 가 그 위 여백을 전부 먹어서,
662 설명 길이가 달라도 여섯 장의 사실 줄이 같은 높이에 선다. */
663 .tile-meta {
664 display: flex; align-items: center; gap: 0.7rem;
665 margin-top: auto; padding-top: 0.35rem;
666 font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.06em;
667 color: var(--ink-4); font-variant-numeric: tabular-nums;
668 transition: color var(--tr-ink);
669 }
670 .tile:hover .tile-meta { color: var(--ink-3); }
671
672 .tile-links { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.1rem; }
673 .tile-links a, .frame-meta a {
674 position: relative; z-index: 2;
675 display: inline-flex; color: var(--ink-4);
676 transition: color var(--tr-ink);
677 }
678 .tile-links a:hover, .frame-meta a:hover { color: var(--accent); }
679
680 /* 아이콘만 있는 링크에는 이름이 안 보인다. title 속성은 브라우저가 1초쯤 뒤에
681 자기 모양으로 띄우는데, 그 지연과 모양이 사이트와 무관하다. 같은 값을
682 data-tooltip 으로도 주고 우리가 그린다. aria-label 이 접근성을 이미 책임지므로
683 이건 순수하게 눈에 보이는 쪽이고, 그래서 aria-hidden 이다. */
684 [data-tooltip] { position: relative; }
685 [data-tooltip]::after {
686 content: attr(data-tooltip);
687 position: absolute; bottom: calc(100% + 0.5rem); left: 50%;
688 transform: translateX(-50%);
689 padding: 0.28rem 0.5rem; white-space: nowrap; pointer-events: none;
690 background: var(--bg-3); color: var(--ink-2);
691 border: 1px solid var(--rule-2); border-radius: var(--radius);
692 font-size: 0.62rem; letter-spacing: 0.14em; text-transform: uppercase;
693 opacity: 0; transition: opacity var(--tr-ink);
694 }
695 [data-tooltip]:hover::after, [data-tooltip]:focus-visible::after { opacity: 1; }
696 html[lang="ko"] [data-tooltip]::after { letter-spacing: 0; text-transform: none; }
697
698 /* 타일 전체가 상세 페이지로 가는 링크. 아이콘만 위로 올린다. */
699 .tile-link::after { content: ""; position: absolute; inset: 0; }
700
701 /* 언어 배지 — 작고 넓게 벌어진 대문자. */
702 .lang {
703 font-family: var(--mono); font-size: 0.62rem;
704 letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-3);
705 }
706 .stars { font-variant-numeric: tabular-nums; }
707
708 /* ---------- project mosaic ---------- */
709
710 /* 첫 장이 크고 나머지 셋이 옆에 쌓인다. 대칭 격자는 여섯 개가 다 같은 무게라고
711 말하는데, 갈래에는 앞에 세울 것이 있다.
712
713 그림이 있는 프로젝트는 둘뿐이다. 없는 것은 자기 색으로 선다 — 프로젝트마다
714 이미 색이 하나씩 정해져 있고(상세 페이지의 --accent 와 같은 값), 그 색이
715 "그림 없음" 판보다 그 프로젝트에 대해 더 많이 말한다. 그림이 생기면 카탈로그의
716 cover 한 줄로 들어온다. */
717 .mosaic {
718 display: grid;
719 grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
720 grid-template-rows: repeat(3, minmax(0, 1fr));
721 gap: clamp(0.5rem, 1.5vw, 1rem);
722 }
723 /* 첫 장이 왼쪽 열을 통째로 쓴다. :first-child 라 개수가 넷이 아니어도 안 깨진다. */
724 .mosaic > :first-child { grid-row: 1 / -1; }
725
726 .mtile {
727 position: relative; display: flex; align-items: flex-end;
728 overflow: hidden;
729 border: 1px solid var(--rule); border-radius: var(--radius);
730 background: var(--bg-2);
731 transition: border-color var(--tr-ink);
732 }
733 .mtile:hover { border-color: var(--rule-2); }
734 /* 작은 장의 키를 여기서 정한다. 큰 장은 셋의 합만큼 자라므로 자기 비율이 없다. */
735 .mosaic > :not(:first-child) { aspect-ratio: 2.1 / 1; }
736
737 /* 바탕. 그림이 있으면 그림이, 없으면 그 프로젝트의 색이 깔린다.
738
739 평상시에는 색이 빠져 있고 손이 닿으면 돌아온다. 이 시트의 첫 줄에 적힌
740 전제가 그것이다 — "색은 평상시에 없고 손이 닿을 때만 나온다". 그림에까지
741 그 규칙을 적용한 것이고, 크기나 자리는 그대로이므로 hover 가 색만 바꾼다는
742 규칙도 지켜진다. 박자는 이 시트의 유일한 박자다.
743 (한때 이 탈색을 걷어냈던 적이 있다. 그때는 참조에서 그대로 가져온 값이라
744 뺐고, 지금은 이 사이트의 전제를 그림까지 밀고 간 것이라 뜻이 다르다.) */
745 .mtile-art {
746 position: absolute; inset: 0;
747 display: grid; place-items: center;
748 background: color-mix(in srgb, var(--tint) 30%, var(--bg-2));
749 filter: grayscale(1);
750 transition: filter var(--tr-ink);
751 }
752 .mtile:hover .mtile-art, .mtile:focus-visible .mtile-art { filter: grayscale(0); }
753 .mtile-art img { width: 100%; height: 100%; object-fit: cover; }
754 /* 마크는 잉크가 아니라 그 프로젝트의 색을 진하게 쓴 것이다 — 바탕과 같은 색의
755 두 단이라 한 물건으로 읽힌다. */
756 .mtile-mark {
757 width: 42%; aspect-ratio: 1;
758 background-color: color-mix(in srgb, var(--tint) 62%, var(--ink));
759 -webkit-mask: var(--mark-src) center / contain no-repeat;
760 mask: var(--mark-src) center / contain no-repeat;
761 }
762
763 /* 글은 바닥에 앉고, 그 아래에서 바탕색이 올라와 그림을 덮는다. 밝은 테마에서는
764 밝은 장막이고 어두운 테마에서는 어두운 장막이라, 글은 두 테마 모두에서 --ink
765 한 색으로 읽힌다. 참조 사이트는 다크 전용이라 흰 글자 한 벌로 끝난다. */
766 .mtile-body {
767 position: relative; z-index: 1; width: 100%;
768 display: flex; flex-direction: column; gap: 0.3rem;
769 padding: clamp(1rem, 2.4vw, 1.6rem);
770 /* 글이 앉는 띠는 바탕색으로 꽉 차 있고 그 위에서 풀린다. 그라데이션이 글자
771 높이 안에서 투명해지면 밝은 그림 위의 밝은 글자가 되는데, ommp 의 표지가
772 정확히 그랬다. 두 테마 모두 --bg 로 가므로 글은 --ink 한 색이면 된다. */
773 background: linear-gradient(to top,
774 var(--bg) 0%,
775 var(--bg) 42%,
776 color-mix(in srgb, var(--bg) 72%, transparent) 72%,
777 transparent 100%);
778 }
779 .mtile-title {
780 font-size: clamp(0.95rem, 0.9rem + 0.3vw, 1.05rem);
781 font-weight: var(--w-item); letter-spacing: var(--track-set);
782 color: var(--ink);
783 }
784 .mtile-blurb {
785 max-width: 44ch; font-size: 0.8rem; line-height: 1.5;
786 color: var(--ink-2); text-wrap: pretty;
787 }
788 .mtile-stars {
789 margin-top: 0.15rem;
790 font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.08em;
791 font-variant-numeric: tabular-nums; color: var(--ink-3);
792 }
793 /* 큰 장은 글도 한 단 크다 — 크기가 순서를 말한다. */
794 .mosaic > :first-child .mtile-title { font-size: clamp(1.1rem, 1rem + 0.5vw, 1.35rem); }
795
796 /* 여기서부터 작은 장의 키는 글이 정한다. 비율을 고정해 두면 폭이 줄면서 상자가
797 글보다 먼저 작아져 잘린다 — 카드에서 겪은 것과 같은 어긋남이다. 그림은 어차피
798 잘리는 물건이므로 여기서는 글이 이기게 둔다. 큰 장은 셋의 합만큼 자란다. */
799 @media (max-width: 72rem) {
800 .mosaic > :not(:first-child) { aspect-ratio: auto; }
801 }
802
803 /* 좁아지면 한 열로 쌓고 전부 같은 비율이 된다. 두 열로 두면 작은 장이 글을
804 담기에 좁아진다. */
805 @media (max-width: 46rem) {
806 .mosaic { grid-template-columns: 1fr; grid-template-rows: none; }
807 .mosaic > :first-child { grid-row: auto; }
808 .mosaic > * { aspect-ratio: 2 / 1; }
809 }
810
811 /* ---------- ledger ---------- */
812
813 .ledger { list-style: none; margin: 0; padding: 0; }
814
815 .ledger-year {
816 margin-top: clamp(2.5rem, 6vw, 4rem);
817 text-align: center;
818 font-family: var(--mono); font-size: 0.68rem;
819 letter-spacing: 0.22em; color: var(--ink-4);
820 }
821 .ledger-year:first-child { margin-top: 0; }
822
823 /* 행 사이에 선이 없다. 간격이 나눈다.
824
825 손이 닿아도 바닥이 안 바뀐다. 회색 판이 깔리면 그 행만 다른 재질이 된 것처럼
826 보이고, 목록을 훑는 동안 판이 행에서 행으로 따라다닌다 — 읽는 자리에서
827 움직이는 것이 하나 생기는 셈이다. 이 사이트의 hover 는 색만 바꾼다는 규칙에
828 맞춰, 여기서도 바뀌는 것은 날짜와 제목의 잉크뿐이다.
829
830 가로 패딩과 같은 크기의 음수 마진이 남아 있는 것은 판을 깔기 위해서가 아니라
831 누를 수 있는 자리를 글자 폭보다 넓게 두기 위해서다. 글은 제자리에 선다.
832 키보드 포커스는 전역 :focus-visible 링이 진다. */
833 .ledger-link {
834 display: grid;
835 grid-template-columns: clamp(5rem, 11vw, 8rem) minmax(0, 1fr);
836 align-items: baseline; gap: clamp(1rem, 2.5vw, 2rem);
837 padding: 1.1rem 1rem; margin-inline: -1rem;
838 }
839
840 .ledger-date {
841 font-family: var(--mono); font-size: 0.72rem;
842 letter-spacing: 0.12em; text-transform: uppercase;
843 font-variant-numeric: tabular-nums;
844 color: var(--ink-4); transition: color var(--tr-ink);
845 }
846 .ledger-link:hover .ledger-date { color: var(--accent); }
847
848 .ledger-body { display: block; min-width: 0; }
849 .ledger-title {
850 display: block;
851 font-size: clamp(1.15rem, 1rem + 0.7vw, 1.55rem);
852 font-weight: var(--w-item); letter-spacing: var(--track-set); line-height: 1.25;
853 color: var(--ink-2); transition: color var(--tr-ink); text-wrap: balance;
854 /* 홈의 글 갈래가 넓은 방으로 옮겨 가면서 이 열이 1080px 까지 열렸다. 제목은
855 한 줄로 읽는 물건이라 폭에 상한이 필요하다 — 설명(58ch)에는 이미 있었다. */
856 max-width: 42ch;
857 }
858 .ledger-link:hover .ledger-title { color: var(--ink); }
859 .ledger-desc {
860 display: block; margin-top: 0.3rem; max-width: 58ch;
861 font-size: 0.88rem; color: var(--ink-3);
862 }
863
864 @media (max-width: 40rem) {
865 .ledger-link { grid-template-columns: 1fr; gap: 0.5rem; padding-inline: 0; margin-inline: 0; }
866 }
867
868 /* 홈의 맨 위 글. 그림과 이름표가 테두리 하나 안에 같이 있다 — 미술관 라벨과
869 작품의 관계다. 전에는 그림을 가운데 세우고 글을 그 아래 따로 세웠는데, 폭도
870 정렬도 달라서 두 물건으로 읽혔다.
871
872 /projects/ 의 액자는 테두리를 그림에만 두르고 캡션을 아래에 띄운다. 그건 여러
873 점이 나란히 걸릴 때의 문법이고, 여기는 한 점뿐이라 라벨이 붙어 있어야 한 작품이
874 된다.
875
876 호버는 색만 바꾼다: 테두리가 한 단, 날짜가 액센트로, 제목이 잉크로. 원장의
877 행과 같은 응답이다. */
878 .feature {
879 display: grid;
880 grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
881 /* 카드의 크기는 글이 정하지 않는다. 어느 글이 맨 위에 오든 같은 상자다.
882 8/3 은 지어낸 값이 아니라 두 열에서 따라 나온다: 그림이 카드 폭의 0.6 을
883 차지하고 그 그림이 16:10 이므로 카드 높이 = 0.6W ÷ 1.6 = W ÷ 2.667 이다.
884 여기 비율을 걸어 두면 이름표가 행을 늘리지 못하고, 그림은 자기 칸을 채우기만
885 하면 되므로 aspect-ratio 를 따로 갖지 않는다. */
886 aspect-ratio: 8 / 3;
887 /* 이 카드 안의 크기는 창이 아니라 **카드**를 따른다. 아래 cqw 가 그 단위다.
888 창에 매달았더니 카드는 좁아지는데 글자는 그만큼 안 줄어서, 920px 근처에서
889 제목의 디센더와 설명 둘째 줄이 상자 밖으로 밀려 잘렸다. 상자와 글자가 같은
890 수에 매여 있으면 그 어긋남이 생길 자리가 없다. */
891 container-type: inline-size;
892 border: 1px solid var(--rule);
893 border-radius: var(--radius);
894 overflow: hidden;
895 background: var(--bg-2);
896 text-align: left;
897 transition: border-color var(--tr-ink);
898 }
899 .feature:hover { border-color: var(--rule-2); }
900 /* 미리보기가 없는 글은 한 칸이다. 빈 칸을 남기면 그 글만 뭔가 빠진 것처럼 보인다. */
901 .feature:not(:has(.feature-media)) { grid-template-columns: 1fr; }
902
903 /* 그림은 자기 칸을 채운다. 높이는 이름표가 정하고 그림이 거기에 맞춰 잘린다 —
904 16:9 를 고정하면 제목이 길어질 때 두 칸의 키가 어긋난다.
905 손이 닿으면 안에서 돌아간다. 상자는 자리도 크기도 안 바꾸므로, 호버에
906 레이아웃이 움직이지 않는다는 규칙은 그대로다.
907 poster 는 영상의 첫 프레임이다. 시작할 때도 되감을 때도 같은 그림이라 이음매가
908 없다. */
909 .feature-media {
910 display: block; margin: 0;
911 /* 높이 바닥을 따로 두지 않는다. width/height 속성이 16:9 를 알려 주므로 그
912 비율이 그대로 행 높이의 바닥이 되고, 라벨이 더 길면 라벨이 이긴다. 예전의
913 clamp 는 어느 폭에서도 이 값을 넘지 못해서 아무 일도 하지 않았다. */
914 width: 100%; height: 100%;
915 /* 16:10 은 이 사이트의 그림 비율이다 — /projects/ 의 .frame-media 가 같은 값을
916 쓴다. 이 카드만 <video> 의 width/height 속성이 주는 16:9 를 그대로 행 높이로
917 써서, 카드가 3.09:1 로 납작했다(참조는 2.66:1). CSS 비율이 내재 비율을
918 덮으므로 행은 이제 max(이름표의 자연 높이, 그림 폭 ÷ 1.6) 이고, 제목이
919 길어지면 이름표가 이긴다.
920 좌우가 잘리는데 이 포스터는 만다라도 인물도 가운데 있어서 잘려 나가는 것은
921 배경이다. 반대로 납작하게 두면 세로가 잘리고, 그때 잘리는 것은 머리다. */
922 aspect-ratio: 16 / 10;
923 object-fit: cover;
924 border-right: 1px solid var(--rule);
925 background: var(--bg-3);
926 }
927
928 .feature-label {
929 display: flex; flex-direction: column; justify-content: center;
930 gap: clamp(0.7rem, 1.9cqw, 1.2rem); min-width: 0;
931 /* 상자가 고정이므로 넘칠 수 있는 쪽은 글이다. 마지막 방어선. */
932 overflow: hidden;
933 padding: clamp(1.1rem, 3.8cqw, 2.4rem);
934 }
935 .feature-date {
936 font-family: var(--mono); font-size: 0.7rem;
937 letter-spacing: 0.14em; font-variant-numeric: tabular-nums;
938 color: var(--ink-4); transition: color var(--tr-ink);
939 }
940 .feature:hover .feature-date { color: var(--accent); }
941 .feature-title {
942 margin: 0;
943 /* 카드 폭의 3.7% — 1102px 카드에서 40.8px 이고, 이름표의 측정폭 353px 중
944 제목이 약 306px 을 쓴다. 제목이 자기 열을 거의 채우는 것이 이 카드가
945 헤드라인처럼 읽히는 이유다(참조는 353 중 353).
946 cqw 라 카드가 좁아지면 글자도 같은 비율로 좁아진다. rem 하한은 아주 좁은
947 카드에서 읽히는 크기의 바닥이다. */
948 font-size: clamp(1.25rem, 3.7cqw, 2.3rem);
949 font-weight: var(--w-section); letter-spacing: var(--track-air); line-height: 1.2;
950 color: var(--ink-2); transition: color var(--tr-ink);
951 /* 세 줄까지. 1440 에서 이름표 안쪽이 372px 이고 날짜·설명·문·간격이 125px 을
952 쓰므로 제목에 247px 이 남는다 — 세 줄이 178px 이라 어떤 제목이 와도 들어온다.
953
954 -webkit-line-clamp 를 쓰지 않는다. 이 조합에서 display 가 flow-root 로
955 계산돼 clamp 자체가 죽고 overflow:hidden 만 남았는데, 그러면 상자 높이가
956 line-height(1.2em)라 글자의 실제 줄상자(1.44em)가 6px 잘린다 — "Beginning"
957 의 g 아래가 그렇게 날아갔다. 줄 수는 max-height 로 세고, padding-bottom 이
958 디센더가 앉을 자리를 만든다. 둘 다 em 이라 크기를 바꿔도 따라온다. */
959 padding-bottom: 0.25em;
960 max-height: calc(3 * 1.2em + 0.25em);
961 overflow: hidden;
962 /* 이 시트의 모든 제목이 balance 다. 한때 여기만 pretty 로 바꿔 두고 "balance 는
963 제목을 한 줄로 붙인다"고 적었는데, 재 보니 39자 제목에서 둘 다 세 줄이고
964 balance 쪽이 줄 길이만 고르게 맞춘다. 틀린 이유로 예외를 만들 뻔했다. */
965 text-wrap: balance;
966 }
967 .feature:hover .feature-title { color: var(--ink); }
968 .feature-desc {
969 margin: 0; max-width: 38ch; color: var(--ink-3); text-wrap: pretty;
970 font-size: clamp(0.85rem, 1.65cqw, 1rem);
971 /* 두 줄까지. 줄 간격이 1.65em 이라 글자 줄상자(1.44em)보다 넉넉해서 여기는
972 따로 자리를 만들 필요가 없다. */
973 line-height: 1.65;
974 max-height: calc(2 * 1.65em);
975 overflow: hidden;
976 }
977
978 /* 글 덩이의 아래 끝. 문(.door)과 같은 물건이다 — 밑줄 그은 마이크로캡스.
979 align-self 가 없으면 flex 열에서 가로로 늘어나 밑줄이 라벨 폭을 가로지른다. */
980 .feature-cta {
981 align-self: start;
982 padding-bottom: 0.3rem;
983 font-size: 0.7rem; letter-spacing: 0.22em; text-transform: uppercase;
984 color: var(--ink-3);
985 border-bottom: 1px solid var(--rule-2);
986 transition: color var(--tr-ink), border-color var(--tr-ink);
987 }
988 /* 밑줄은 잉크 사다리에서 한 단만 오른다. 액센트는 이 카드에 하나뿐이고 날짜가
989 그걸 갖는다 — 위 규칙에 그렇게 적어 놓고 여기서 두 번째를 켰었다. */
990 .feature:hover .feature-cta { color: var(--ink-2); border-bottom-color: var(--rule-3); }
991
992 /* 좁아지면 그림이 위로 올라간다. 옆으로 두면 두 칸 다 못 쓸 만큼 좁아진다.
993 64rem 인 이유: 그 아래에서는 고정 8/3 상자에 네 줄이 안 들어간다. 상자를
994 늘리는 대신 쌓는다 — 쌓인 카드는 글이 크기를 정해도 되고, 그게 이 폭에서
995 맞는 답이다. */
996 @media (max-width: 56rem) {
997 .feature { grid-template-columns: 1fr; aspect-ratio: auto; }
998 .feature-media {
999 aspect-ratio: 16 / 10;
1000 border-right: 0; border-bottom: 1px solid var(--rule);
1001 }
1002 }
1003
1004 /* ---------- buttons ---------- */
1005
1006 .btn-row { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; margin-top: 1.75rem; }
1007
1008 .btn {
1009 display: inline-flex; align-items: center; gap: 0.5rem;
1010 height: 2.5rem; padding-inline: 1.15rem;
1011 border: 1px solid var(--rule-2); border-radius: var(--radius);
1012 font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase;
1013 color: var(--ink-2);
1014 transition: color var(--tr-ink), border-color var(--tr-ink), background var(--tr-ink);
1015 }
1016 /* 채운 버튼은 잉크로 채운다. 액센트로 채우면 그게 첫 화면에서 제일 큰 색이 된다. */
1017 .btn-primary { background: var(--ink); border-color: var(--ink); color: var(--bg); }
1018 .btn-primary:hover { background: var(--ink); filter: brightness(1.08); }
1019 .btn-ghost:hover { color: var(--ink); border-color: var(--rule-3); }
1020 .btn-text { border-color: transparent; padding-inline: 0.35rem; }
1021 .btn-text:hover { color: var(--accent); }
1022
1023 .back {
1024 display: inline-flex; align-items: center; gap: 0.45rem;
1025 margin-top: 2rem;
1026 font-size: 0.7rem; letter-spacing: 0.18em; text-transform: uppercase;
1027 color: var(--ink-4);
1028 transition: color var(--tr-ink);
1029 }
1030 .back:hover { color: var(--ink-2); }
1031
1032 /* ---------- project detail ---------- */
1033
1034 /* 프로젝트마다 자기 색조를 갖는다. 전역 --accent 를 이 페이지 안에서만
1035 덮으므로 eyebrow 대시·특징 번호·링크·포커스가 한꺼번에 따라간다. 값이 없는
1036 프로젝트는 인라인 style 이 아예 안 붙어서 사이트 기본값을 쓴다.
1037 대비 범위는 좁게 잡는다 — 중간 명도, 낮은 채도. 니어블랙 위에서 읽히면서도
1038 페이지를 자기 색으로 물들이지는 않는 띠다. */
1039 .project { padding-bottom: clamp(3rem, 8vw, 6rem); }
1040
1041 /* 브리프는 읽는 폭 안에 살고 제목도 한 단계 작다. 등급 차이가 열 개수가
1042 아니라 타이포로 보여야 한다 — 그러지 않으면 "같은 페이지, 더 짧은 것"이다. */
1043 .project[data-tier="brief"] .hero-name { font-size: clamp(2rem, 1.6rem + 1.6vw, 2.8rem); }
1044 .project[data-tier="brief"] .hero { border-bottom: 0; padding-bottom: clamp(1.5rem, 3vw, 2rem); }
1045
1046 .hero {
1047 display: grid; grid-template-columns: 1.35fr 1fr;
1048 gap: clamp(1.5rem, 5vw, 4rem); align-items: center;
1049 padding-block: clamp(2rem, 5vw, 3.5rem) clamp(2.5rem, 6vw, 4rem);
1050 border-bottom: 1px solid var(--rule);
1051 }
1052 .hero-main { min-width: 0; }
1053
1054 /* eyebrow 앞의 액센트 대시. 제품 명패의 시작을 표시한다. */
1055 .hero .eyebrow { display: flex; align-items: center; gap: 0.75rem; }
1056 .hero .eyebrow::before {
1057 content: ""; width: 1.75rem; height: 1px; background: var(--accent); flex: none;
1058 }
1059
1060 .hero-name {
1061 display: flex; align-items: center; gap: 0.6rem; margin: 0;
1062 font-size: clamp(2.6rem, 1.9rem + 2.6vw, 3.8rem);
1063 font-weight: var(--w-display); letter-spacing: var(--track-air); line-height: 1.05;
1064 }
1065 .hero-name .chip-mark { width: 1.05em; height: 1.05em; }
1066
1067 .hero-tagline { margin: 1.1rem 0 0; max-width: 46ch; color: var(--ink-2); text-wrap: pretty; }
1068
1069 .badges { display: flex; flex-wrap: wrap; gap: 1.25rem; margin: 1.5rem 0 0; padding: 0; list-style: none; }
1070 .badges li {
1071 font-family: var(--mono); font-size: 0.66rem;
1072 letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-4);
1073 }
1074
1075 .hero .btn-row { justify-content: flex-start; }
1076
1077 .hero-aside { display: grid; place-items: center; }
1078 .medallion {
1079 display: grid; place-items: center;
1080 width: min(100%, 20rem); aspect-ratio: 1;
1081 border: 1px solid var(--rule); border-radius: var(--radius);
1082 background: var(--bg-2); overflow: hidden;
1083 }
1084 .medallion img { width: 76%; height: 76%; object-fit: contain; }
1085 .medallion-mark::before {
1086 content: ""; width: 72%; height: 72%;
1087 background-color: var(--mark-ink);
1088 -webkit-mask: var(--mark-src) center / contain no-repeat;
1089 mask: var(--mark-src) center / contain no-repeat;
1090 }
1091
1092 @media (max-width: 52rem) {
1093 .hero { grid-template-columns: minmax(0, 1fr); text-align: center; }
1094 .hero .eyebrow, .hero-name, .hero .btn-row { justify-content: center; }
1095 .hero-tagline, .badges { margin-inline: auto; justify-content: center; }
1096 /* 좁을 때 옆칸은 지우지 않고 위로 올린다. 지우면 그 프로젝트만 그림이 없는
1097 것처럼 보인다. */
1098 .hero-aside { order: -1; }
1099 .medallion { width: min(60%, 13rem); }
1100 }
1101
1102 /* ---------- facts ---------- */
1103
1104 .facts {
1105 display: flex; flex-wrap: wrap; justify-content: center;
1106 margin-block: clamp(2rem, 5vw, 3rem);
1107 padding-block: clamp(1.5rem, 3vw, 2rem);
1108 border-bottom: 1px solid var(--rule);
1109 }
1110 .fact {
1111 flex: 1 1 9rem; display: flex; flex-direction: column; gap: 0.3rem;
1112 align-items: center; text-align: center; padding-inline: 1rem;
1113 border-left: 1px solid var(--rule);
1114 }
1115 .fact:first-child { border-left: 0; }
1116 .fact-value {
1117 font-size: clamp(1.9rem, 1.5rem + 1.6vw, 2.9rem);
1118 font-weight: var(--w-display); letter-spacing: 0.005em; line-height: 1;
1119 font-variant-numeric: tabular-nums;
1120 }
1121 .fact-label {
1122 font-size: 0.64rem; letter-spacing: 0.22em; text-transform: uppercase;
1123 color: var(--ink-4);
1124 }
1125
1126 /* ---------- lattice ---------- */
1127
1128 .block { padding-block: clamp(2.5rem, 6vw, 4rem); border-bottom: 1px solid var(--rule); }
1129 .block:last-child { border-bottom: 0; }
1130 .block-head { margin-bottom: clamp(1.75rem, 4vw, 2.5rem); text-align: center; }
1131 .block-title {
1132 margin: 0; font-size: clamp(1.5rem, 1.25rem + 1.1vw, 2.1rem);
1133 font-weight: var(--w-section); letter-spacing: var(--track-air); text-wrap: balance;
1134 }
1135 .block-intro { margin: 0.8rem auto 0; max-width: 56ch; color: var(--ink-3); }
1136
1137 /* 선은 음수 마진으로 겹친 1px 이다. 개수와 열수가 무엇이든 맞아 들어가고
1138 :nth-child 규칙이 필요 없다. 20rem 은 임의값이 아니다 — 71rem 안에서 열이
1139 3개가 되고, 특징을 여섯 개로 쓰면 두 줄이 정확히 찬다. */
1140 .lattice {
1141 display: grid;
1142 grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
1143 border-top: 1px solid var(--rule);
1144 border-left: 1px solid var(--rule);
1145 }
1146 .cell {
1147 position: relative; isolation: isolate;
1148 padding: clamp(1.25rem, 3vw, 1.75rem) clamp(1rem, 2vw, 1.5rem);
1149 min-width: 0;
1150 margin: 0 -1px -1px 0;
1151 border-right: 1px solid var(--rule);
1152 border-bottom: 1px solid var(--rule);
1153 }
1154 /* hover 에서 위로 올린다. 그러지 않으면 이웃에 가려 두 변만 밝아진다. */
1155 .cell:hover { z-index: 1; border-color: var(--rule-2); }
1156 .cell { transition: border-color var(--tr-ink); }
1157
1158 .cell-index {
1159 display: block; font-family: var(--mono); font-size: 0.66rem;
1160 letter-spacing: 0.18em; color: var(--ink-4); margin-bottom: 0.7rem;
1161 transition: color var(--tr-ink);
1162 }
1163 .cell:hover .cell-index { color: var(--accent); }
1164 .cell-title { margin: 0; font-size: 1rem; font-weight: var(--w-label); letter-spacing: var(--track-set); }
1165 .cell-desc { margin: 0.5rem 0 0; font-size: 0.86rem; color: var(--ink-3); text-wrap: pretty; }
1166
1167 /* ---------- terminal ---------- */
1168
1169 .term {
1170 border: 1px solid var(--rule-2); border-radius: var(--radius);
1171 overflow: hidden; background: var(--bg-recess);
1172 max-width: 52rem; margin-inline: auto;
1173 }
1174 .term-bar {
1175 display: flex; align-items: center; gap: 0.7rem;
1176 padding: 0.55rem 0.85rem;
1177 border-bottom: 1px solid var(--rule); background: var(--bg-2);
1178 }
1179 .term-dots { display: inline-flex; gap: 0.32rem; }
1180 .term-dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--rule-3); }
1181 .term-dots i:first-child { background: var(--ink-4); }
1182 .term-name { font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.1em; color: var(--ink-4); }
1183
1184 .term-body {
1185 margin: 0; padding: 1.2rem 1.3rem; overflow-x: auto;
1186 font-family: var(--mono); font-size: 0.8rem; line-height: 1.85; white-space: pre;
1187 }
1188 .term-body code { font: inherit; background: none; padding: 0; border: 0; }
1189 .term-body span { display: block; }
1190 .term-cmd { color: var(--ink); }
1191 /* 주석은 기울이고, 출력은 중립 회색이다. 출력에 색을 주면 그게 결과가 아니라
1192 상태로 읽힌다. */
1193 .term-comment { color: var(--ink-4); font-style: italic; }
1194 .term-out { color: var(--ink-3); }
1195 .term-caption {
1196 margin: 0.8rem auto 0; max-width: 52rem; text-align: center;
1197 font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.06em; color: var(--ink-4);
1198 }
1199
1200 /* ---------- cta ---------- */
1201
1202 .cta { margin-top: clamp(3rem, 7vw, 5rem); padding-block: clamp(2.5rem, 6vw, 5rem); text-align: center; }
1203 .cta-inner { max-width: 46ch; margin-inline: auto; }
1204 .cta-title {
1205 margin: 0; font-size: clamp(1.6rem, 1.3rem + 1.4vw, 2.4rem);
1206 font-weight: var(--w-display); letter-spacing: var(--track-air); text-wrap: balance;
1207 }
1208 .cta-text { margin: 0.9rem 0 0; color: var(--ink-3); }
1209
1210 /* ---------- about ---------- */
1211
1212 .about-head { text-align: center; }
1213 .portrait { margin: 0 auto clamp(1.5rem, 4vw, 2.25rem); width: min(100%, 12rem); }
1214 .portrait img {
1215 display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
1216 border: 1px solid var(--rule-2); border-radius: var(--radius);
1217 }
1218
1219 /* ---------- toc & pager ---------- */
1220
1221 .toc { margin: 2rem auto 2.5rem; max-width: var(--read); padding: 1rem 1.25rem; border-left: 1px solid var(--rule-2); }
1222 .toc-label {
1223 margin: 0 0 0.5rem; font-size: 0.66rem;
1224 letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-4);
1225 }
1226 .toc ul { list-style: none; margin: 0; padding: 0; }
1227 .toc ul ul { padding-left: 1rem; }
1228 .toc li { margin: 0.3rem 0; font-size: 0.88rem; }
1229 .toc a { color: var(--ink-3); transition: color var(--tr-ink); }
1230 .toc a:hover { color: var(--accent); }
1231
1232 /* ---------- reading rail ---------- */
1233
1234 /* 목차의 두 번째 모습. 넓은 화면에서 오른쪽 가장자리에 서고, 절마다 세로
1235 헤어라인 한 칸이며 칸의 높이가 그 절이 글에서 차지하는 몫이다. 목록은 절이
1236 몇 개인지 말하고 지도는 어느 절이 긴지 말한다.
1237
1238 레일이 실제로 만들어졌을 때만 목록이 물러난다(.has-rail 은 ui.js 가 붙인다).
1239 스크립트가 못 오면 목록이 그대로 남고, 그게 이 장치의 폴백이다.
1240
1241 처음 판은 본문 칼럼에 붙여 놓고 눈금 자체만 손을 받게 했다. 재 보니 레일이
1242 차지한 넓이의 14% 만 반응했고, 눈금에서 이름으로 커서를 옮기면 이름이
1243 대부분 사라졌다 — 이름과 세로로 겹치는 28% 구간에서만 건너갈 수 있었다.
1244 가끔 되는 조작은 안 되는 조작보다 나쁘다. 지금은 여백 전체가 손을 받는다. */
1245
1246 .rail { display: none; }
1247
1248 /* 이 시트의 첫 min-width 다. 나머지는 전부 max-width — 넓게 시작해서 좁아지면
1249 뺀다는 자세이고, 이것만 예외인 이유는 레일이 없던 자리를 요구하는 유일한
1250 요소이기 때문이다. */
1251 @media (min-width: 80rem) {
1252 .rail {
1253 display: grid;
1254 /* 이름이 안쪽, 눈금이 바깥쪽이다. 눈금은 스크롤 막대 옆에 서는 스크롤
1255 가구이고 이름은 글 쪽으로 흐른다 — 반대로 두면 눈이 글에서 멀어지는
1256 방향으로 읽어 나가야 한다. */
1257 grid-template-columns: minmax(0, 1fr) 2px;
1258 column-gap: 0.9rem;
1259 row-gap: 0.3rem;
1260 /* 행 높이는 ui.js 가 grid-template-rows 로 쓴다. 겹침 풀이를 격자에 맡기면
1261 참조 구현이 손으로 짠 두 번의 밀어내기 패스가 통째로 없어지고, 이름이
1262 엉뚱한 눈금 옆에 가는 일이 구조적으로 불가능해진다. */
1263
1264 position: fixed;
1265 /* 창 가장자리에 붙는다. 본문 칼럼에 매달았더니 1920px 에서 레일이 창 끝에서
1266 551px 떨어진 자리에 섰다 — 손이 가는 자리가 아니다. 스크롤 막대를 향해
1267 뻗는 손이 지나가는 띠가 이 화면에서 가장 자주 밟히는 빈 자리이고,
1268 여기 두면 찾으려 하지 않아도 걸린다.
1269 폭은 본문과 겹치지 않는 만큼까지만 — 1280px 에서 28px 이 남는다. */
1270 right: 0;
1271 width: min(16rem, calc(50vw - var(--read) / 2 + var(--gutter) - 1.5rem));
1272 /* 눈금과 창 끝 사이. 스크롤 막대 바로 옆이 이 물건의 자리다 — 멀어질수록
1273 본문 여백에 떠 있는 장식으로 보이고, 붙을수록 스크롤 가구로 읽힌다.
1274 포커스 링이 2px 테두리에 3px 오프셋이라 그만큼은 남긴다. */
1275 padding-right: 0.3rem;
1276 top: 50%; transform: translateY(-50%);
1277 height: min(64vh, 34rem);
1278 /* 마스트헤드(30) 아래. 올라온 이름이 크롬을 덮으면 안 된다. */
1279 z-index: 20;
1280 }
1281
1282 /* 눈금은 세 단의 밝기다: 아직 안 읽은 것, 읽은 것, 지금 읽는 것. 색조는 한
1283 가지뿐이라 2px 에서도 순서가 읽힌다. 한때 읽은 자리를 잉크로, 지금 자리를
1284 주황으로 칠한 적이 있는데 그때는 과거가 현재보다 두 배 진했다 — 두 색을
1285 섞으면 어느 쪽이 앞인지 정할 방법이 없다. 색은 하나고, 순서는 길이가 짓는다. */
1286 /* 아직 안 읽은 자리. 중립 헤어라인이라 주황이 지나간 자리와 안 지나간 자리가
1287 색조로 갈린다. */
1288 .rail-seg { background: var(--rule-3); }
1289
1290 /* 읽은 만큼. 이 사이트의 유일한 색이고, 여기가 그 색이 상시로 서는 유일한
1291 자리다 — 다른 데서는 손이 닿을 때만 나온다. 진행은 한 가지 색이 단조롭게
1292 자라는 것으로만 말한다: 위는 다 차고, 지금 절만 덜 찼고, 아래는 비어 있다.
1293 "지금"을 다른 색으로 칠하지 않는 이유가 그거다. 덜 찬 칸이 이 열에 하나뿐이라
1294 색을 더 쓰면 읽는 사람이 순서를 매길 수 없는 두 번째 변수가 생긴다.
1295
1296 라이트에서 이 주황은 바탕 대비 2.12:1 이다. 낮다 — 그래서 이 레일은 보조
1297 표시이고, 실제 목차는 좁은 화면과 스크립트가 없을 때 그대로 나오는 본문의
1298 목록이 진다. 이름의 현재 표시는 색이 아니라 aria-current 와 잉크가 진다.
1299
1300 높이는 ui.js 가 스크롤에서 직접 쓴다 — 전환을 걸면 화면보다 한 박자 늦게
1301 따라와 거짓말을 한다. 이미 연속값에 매인 것은 애니메이션하지 않는다. */
1302 .rail-fill {
1303 display: block; width: 100%; height: 0;
1304 background: var(--accent);
1305 }
1306
1307 .rail-label {
1308 align-self: start;
1309 /* 오른쪽 정렬이라 이름들이 눈금에 붙어 한 덩이로 읽힌다. 왼쪽 정렬로 두면
1310 길이가 제각각인 세 구절이 빈 자리에 흩어져 목록으로 안 보인다. */
1311 text-align: right;
1312 /* 두 줄까지. 칸의 아래끝 2.2rem 이 정확히 두 줄 높이라, 넘치면 옆 칸을
1313 침범한다. -webkit-line-clamp 를 쓰지 않는 이유는 카드 제목과 같다 — 이
1314 조합에서 display 가 flow-root 로 계산돼 clamp 가 죽고 overflow 만 남으면,
1315 상자가 line-height 만큼이라 글자 줄상자(1.44em)의 아래가 잘린다.
1316 ponytail: 여기 걸리는 제목이 잦아지면 breakpoint 를 84rem 으로 올려 이름
1317 열을 넓히는 쪽이 먼저다. 글자 크기를 줄이는 건 그다음. */
1318 max-height: calc(2 * 1.35em + 0.12em);
1319 padding-bottom: 0.12em;
1320 overflow: hidden;
1321 font-size: 0.72rem; line-height: 1.35;
1322 /* 13.7px 글자다. --ink-3 은 라이트에서 4.23:1 로 AA 아래이고, 여기서 쓰던
1323 hover 액센트는 2.12:1 이라 손을 얹으면 더 안 읽혔다 — 이 열에서 액센트가
1324 두 번째로 틀린 자리다. 밝기로만 간다. */
1325 color: var(--ink-2);
1326 opacity: 0; pointer-events: none;
1327 transition: opacity var(--tr-ink), color var(--tr-ink);
1328 }
1329 .rail-label[aria-current] { color: var(--ink); }
1330
1331 /* 드러나는 것은 숨어 있던 것이지 자리를 옮긴 것이 아니다. 이름은 제자리에서
1332 밝아진다 — 굵어지지도, 밀려나지도 않는다. 여백 전체가 방아쇠라, 한 번 나온
1333 이름까지 커서가 가는 동안 사라지지 않는다. 박자는 이 시트의 유일한 박자다.
1334
1335 눈금은 hover 에 반응하지 않는다. --rule-3 에서 --ink-4 로 옮겼더니 라이트에서
1336 1.78:1 → 1.72:1 로 오히려 흐려졌고 다크에서는 1.76 → 1.77 로 아무 일도
1337 일어나지 않았다 — 두 토큰의 밝기가 두 테마 모두에서 거의 같다. 사이에 낄
1338 단이 없고, 이름 넷이 한꺼번에 나타나는 것이 이미 충분한 대답이다.
1339 좁아지면 줄이는 게 아니라 뺀다. */
1340 .rail:hover .rail-label,
1341 .rail:focus-within .rail-label { opacity: 1; pointer-events: auto; }
1342
1343 /* 손이 얹힌 이름은 밝아진다. 지금 절과 같은 잉크가 되지만 둘을 가르는 것은
1344 색이 아니라 눈금이다 — 지금 절만 덜 찬 칸을 갖는다. 내비도 같은 규칙이다. */
1345 .rail-label:hover { color: var(--ink); }
1346
1347 .has-rail .toc { display: none; }
1348 }
1349
1350 /* ---------- prose ---------- */
1351
1352 .prose { font-size: 1.02rem; line-height: 1.78; }
1353 .prose-intro { margin-bottom: 2.5rem; color: var(--ink-2); text-align: center; }
1354 .project .prose, .prose-intro { max-width: var(--read); margin-inline: auto; }
1355
1356 /* 여백은 형제 사이에만. 타입 셀렉터로 초기화하면 특정성이 높아 아래 규칙을
1357 이기고 문단 사이 여백이 통째로 사라진다. */
1358 .prose > * { margin: 0; }
1359 .prose > * + * { margin-top: 1.2rem; }
1360 .prose p { text-wrap: pretty; }
1361
1362 .prose h2, .prose h3, .prose h4 {
1363 margin: 2.75rem 0 0; line-height: 1.25; letter-spacing: 0.012em;
1364 text-wrap: balance; scroll-margin-top: calc(var(--masthead-h) + 1rem);
1365 }
1366 .prose h2 { font-size: clamp(1.3rem, 1.15rem + 0.7vw, 1.6rem); font-weight: var(--w-section); }
1367 .prose h3 { font-size: 1.12rem; font-weight: var(--w-item); }
1368 .prose h4 { font-size: 1rem; font-weight: var(--w-label); }
1369 .prose h2 + *, .prose h3 + *, .prose h4 + * { margin-top: 0.8rem; }
1370
1371 .prose a { color: var(--ink); text-decoration: underline; text-underline-offset: 0.2em; text-decoration-color: var(--rule-3); }
1372 .prose a:hover { text-decoration-color: var(--accent); }
1373
1374 .prose ol { margin-top: 1.2rem; padding-left: 1.35rem; }
1375 .prose ul { margin-top: 1.2rem; list-style: none; padding-left: 1.1rem; }
1376 .prose li { margin: 0.4rem 0; }
1377 .prose ul > li { position: relative; }
1378 .prose ul > li::before {
1379 content: ""; position: absolute; left: -1.1rem; top: 0.85em;
1380 width: 0.45rem; height: 1px; background: var(--ink-4);
1381 }
1382
1383 .prose blockquote {
1384 margin-top: 1.8rem; padding-left: 1.25rem;
1385 border-left: 1px solid var(--rule-3); color: var(--ink-2);
1386 }
1387
1388 .prose code {
1389 font-family: var(--mono); font-size: 0.86em;
1390 padding: 0.12em 0.35em; border-radius: var(--radius); background: var(--bg-3);
1391 }
1392 .prose pre {
1393 position: relative; margin-top: 1.5rem; padding: 1.1rem 1.2rem; overflow-x: auto;
1394 border: 1px solid var(--rule); border-radius: var(--radius);
1395 background: var(--bg-recess); font-size: 0.82rem; line-height: 1.75;
1396 }
1397 .prose pre code { padding: 0; background: none; font-size: inherit; }
1398
1399 .copy-btn {
1400 position: absolute; top: 0.5rem; right: 0.5rem;
1401 padding: 0.18rem 0.5rem;
1402 border: 1px solid var(--rule-2); border-radius: var(--radius);
1403 background: var(--bg-2); color: var(--ink-4);
1404 font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.1em;
1405 text-transform: uppercase; cursor: pointer; opacity: 0;
1406 transition: opacity var(--tr-ink), color var(--tr-ink), border-color var(--tr-ink);
1407 }
1408 .prose pre:hover .copy-btn, .prose pre:focus-within .copy-btn { opacity: 1; }
1409 .copy-btn:hover { color: var(--ink); border-color: var(--rule-3); }
1410 .copy-btn.is-done { opacity: 1; color: var(--accent); border-color: var(--accent); }
1411
1412 .prose table {
1413 width: 100%; margin-top: 1.75rem; border-collapse: collapse;
1414 font-size: 0.9rem; font-variant-numeric: tabular-nums;
1415 }
1416 .prose th, .prose td { padding: 0.55rem 0.75rem; text-align: left; border-bottom: 1px solid var(--rule); }
1417 .prose th {
1418 font-size: 0.64rem; font-weight: 500;
1419 letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-4);
1420 border-bottom-color: var(--rule-2);
1421 }
1422 .prose td { color: var(--ink-2); }
1423 .prose hr { margin-top: 2.5rem; border: 0; border-top: 1px solid var(--rule); }
1424
1425 .prose .anchor { opacity: 0; color: var(--ink-4); text-decoration: none; transition: opacity var(--tr-ink); }
1426 .prose h2:hover .anchor, .prose h3:hover .anchor, .prose h4:hover .anchor,
1427 .prose .anchor:focus-visible { opacity: 1; }
1428
1429 /* ---------- search palette ---------- */
1430
1431 .palette {
1432 width: min(38rem, calc(100vw - 2rem)); margin-top: 12vh; padding: 0;
1433 border: 1px solid var(--rule-2); border-radius: var(--radius);
1434 background: var(--bg-2); color: var(--ink);
1435 box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
1436 }
1437 .palette::backdrop { background: rgba(1, 1, 2, 0.66); backdrop-filter: blur(4px); }
1438 .palette-form { margin: 0; }
1439 .palette-head {
1440 display: flex; align-items: center; gap: 0.7rem;
1441 padding: 0.85rem 1rem; border-bottom: 1px solid var(--rule); color: var(--ink-4);
1442 }
1443 .palette-input {
1444 flex: 1; min-width: 0; border: 0; background: none; color: var(--ink);
1445 font: inherit; font-size: 0.98rem; outline: none;
1446 }
1447 .palette-close { border: 0; background: none; cursor: pointer; padding: 0; color: var(--ink-4); }
1448 .palette-close kbd { font-family: var(--mono); font-size: 0.64rem; }
1449 .palette-results { list-style: none; margin: 0; padding: 0.4rem; max-height: 56vh; overflow-y: auto; }
1450 .palette-results:empty { display: none; }
1451 .palette-results a { display: block; padding: 0.6rem 0.7rem; border-radius: var(--radius); }
1452 .palette-results li[aria-selected="true"] a, .palette-results a:hover { background: var(--bg-3); }
1453 .palette-title { display: block; font-size: 0.94rem; font-weight: 400; }
1454 .palette-crumb {
1455 display: block; margin-top: 0.15rem;
1456 font-size: 0.66rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-4);
1457 }
1458 .palette-results mark { background: none; color: var(--accent); }
1459 .palette-status {
1460 margin: 0; padding: 0.55rem 1rem; border-top: 1px solid var(--rule);
1461 font-family: var(--mono); font-size: 0.66rem; color: var(--ink-4);
1462 }
1463 .palette-status:empty { display: none; }
1464
1465 /* ---------- colophon ---------- */
1466
1467 /* 여백은 세 줄을 서로 떼어 놓을 만큼만. 크롬은 읽는 물건이 아니라 나가는
1468 길이라, 본문만 한 자리를 차지하면 페이지가 거기서 한 번 더 시작하는 것처럼
1469 보인다. */
1470 .colophon { border-top: 1px solid var(--rule); padding-block: 2rem 2.5rem; }
1471 .colophon-in {
1472 max-width: var(--wide); margin-inline: auto; padding-inline: var(--gutter);
1473 display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
1474 gap: 0.9rem 2rem; text-align: center;
1475 }
1476 .colophon-nav { display: flex; flex-wrap: wrap; justify-content: center; gap: 1.75rem; }
1477 .colophon-nav a {
1478 font-size: 0.66rem; letter-spacing: 0.22em; text-transform: uppercase;
1479 color: var(--ink-4); transition: color var(--tr-ink);
1480 }
1481 .colophon-nav a:hover { color: var(--ink-2); }
1482 /* 갈래 링크와 소셜은 다른 줄에 선다 — 안을 도는 것과 밖으로 나가는 것이다. */
1483 .colophon-social { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem; flex-basis: 100%; }
1484 /* 아이콘만 남긴다. GitHub 와 Instagram 은 마크만으로 읽히는 몇 안 되는 표지라
1485 낱말이 하는 일이 없다 — 이름은 aria-label 이 진다. */
1486 .colophon-social a {
1487 display: inline-flex; align-items: center; justify-content: center;
1488 width: 1.9rem; height: 1.9rem;
1489 font-size: 1rem; color: var(--ink-3);
1490 transition: color var(--tr-ink);
1491 }
1492 .colophon-social a:hover { color: var(--accent); }
1493 .colophon-social svg { flex: none; }
1494
1495 .colophon-note { margin: 0; flex-basis: 100%; font-size: 0.7rem; letter-spacing: 0; color: var(--ink-4); }
1496 .colophon-note a { color: var(--ink-3); transition: color var(--tr-ink); }
1497 .colophon-note a:hover { color: var(--accent); }
1498
1499 /* ---------- Korean ---------- */
1500
1501 /* 한국어는 라틴 타이포 장치 셋을 깨뜨린다. letter-spacing 은 음절 블록을 벌리고,
1502 text-transform 은 아무 일도 하지 않으며, monospace 는 한글이 프로포셔널로
1503 폴백하는데 공백만 mono 폭이라 어절 사이가 두 배로 벌어진다.
1504
1505 여기 걸리는 것은 **한국어가 들어가는 자리만**이다. 크롬(내비·푸터·컨트롤·
1506 문 링크)은 두 언어에서 영어로 고정이므로 대문자와 자간을 그대로 둔다 —
1507 되돌리면 한국어 페이지에서만 라벨이 소문자로 나와 두 언어가 달라 보인다. */
1508 html[lang="ko"] .eyebrow,
1509 html[lang="ko"] .fact-label,
1510 html[lang="ko"] .prose th,
1511 html[lang="ko"] .crumbs,
1512 html[lang="ko"] .palette-crumb,
1513 html[lang="ko"] .btn,
1514 html[lang="ko"] .back,
1515 html[lang="ko"] .badges li {
1516 font-family: var(--sans);
1517 letter-spacing: 0;
1518 text-transform: none;
1519 }
1520
1521 /* 초경량은 한글에서도 나오지만 아주 큰 크기에서만 버틴다. 한글은 같은 굵기에서
1522 획이 훨씬 많아 라틴보다 먼저 뭉개지고, 작아지면 획이 끊어져 보인다.
1523 사다리 전체를 한 단씩 올린다 — 선택자마다 예외를 적으면 라틴 쪽 사다리를
1524 고칠 때 이쪽이 조용히 어긋난다. */
1525 html[lang="ko"] {
1526 --w-display: 300;
1527 --w-section: 350;
1528 --w-item: 400;
1529 --w-label: 500;
1530 /* 자간도 여기서 끈다. letter-spacing 은 한글에서 음절 블록 사이를 벌려
1531 한 낱말이 여러 낱말로 보이게 만든다 — 라틴에서 숨을 주는 그 값이
1532 한글에서는 정확히 반대로 작동한다. */
1533 --track-air: 0;
1534 --track-set: 0;
1535 }
1536
1537 /* 숫자와 날짜는 한국어에서도 mono 다. 숫자에는 위 문제가 없고 자릿수가 맞아야
1538 목록이 줄을 이룬다. */
1539 html[lang="ko"] .ledger-date,
1540 html[lang="ko"] .feature-date,
1541 html[lang="ko"] .ledger-year,
1542 html[lang="ko"] .term-body,
1543 html[lang="ko"] .stars { font-family: var(--mono); }
1544
1545 /* ---------- reduced motion ---------- */
1546
1547 @media (prefers-reduced-motion: reduce) {
1548 html { scroll-behavior: auto; }
1549 *, *::before, *::after {
1550 animation-duration: 0.01ms !important;
1551 animation-iteration-count: 1 !important;
1552 transition-duration: 0.01ms !important;
1553 }
1554 /* 전환만 끄면 변형된 자리에 멈춘다. 종점도 같이 중화한다. */
1555 .js .hero-home [data-rise] { animation: none; opacity: 1; transform: none; }
1556 .frame:hover .frame-media,
1557 .frame:hover .frame-media img,
1558 .tile-links a:hover { transform: none; }
1559 }