/* sqzer.com
 *
 * 쿨 그래파이트 필드에 뜻을 가진 액센트 둘. --live 는 지금 살아 있는 것(활성,
 * 현재 위치, 주 동작)이고 --mark 는 표시하는 것(보관됨, 지표, 눈금)이다. 색이
 * 취향이 아니라 상태를 말하므로, 어느 쪽인지 몰라서 둘 다 보는 일이 없다.
 *
 * 목소리는 둘이다. 본문은 시스템 산세리프 — 한글 웹폰트는 제대로 갖추면 2MB
 * 라서, 이미 기계에 있는 것을 쓴다. 두 번째 목소리는 JetBrains Mono 이고
 * 라틴 전용 21KB 다. 날짜, 배지, 명령, 워드마크가 그 목소리를 쓴다.
 *
 * 테마는 [data-theme] 이 정한다. prefers-color-scheme 이 아닌 이유는 생성된
 * 하이라이트 스타일시트가 같은 속성을 보기 때문이다. 둘이 서로 다른 근거로
 * 결정하면 라이트 OS 를 쓰는 사람이 어두운 페이지에 밝은 코드를 본다.
 */

@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/jetbrains-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/jetbrains-mono-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

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

:root {
  color-scheme: dark;

  --bg-recess: #06080a;
  --bg:        #0b0e12;
  --bg-raised: #12161c;
  --bg-hi:     #1a1f26;

  --ink:   #e8ecf1;
  --ink-2: #9aa4b0;
  --ink-3: #6b7580;
  --ink-4: #333b45;

  --rule:     #1a1f27;
  --rule-hi:  #262d37;
  --rule-str: #3a434f;

  /* 색이 아니라 삼원소다. 토큰 하나로 alpha 0.03 부터 0.75 까지 전부 나온다 —
     hex 였다면 매번 color-mix 를 거쳐야 했을 값들이다. */
  --glow: 232, 236, 241;

  --live: #4fb6a5;
  --mark: #d9a441;

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

  --radius: 4px;
  --masthead-h: 3.5rem;
  --control-h: 2rem;
  --gutter: clamp(1.25rem, 4vw, 3rem);

  /* 원장의 날짜 거터. 한 곳에만 두면 홈과 목록이 같은 자리에서 정렬된다. */
  --key-width: 4.5rem;

  /* 한 hover 안에서 잉크는 빠르고 빛은 느리다. 곡선은 둘뿐이고 세 번째를
     들이지 않는다 — 속도가 여러 개면 같은 물건이라는 느낌이 사라진다. */
  --tr-ink: 0.22s ease;
  --tr-light: 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}

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

  --bg-recess: #eceef1;
  --bg:        #f7f8fa;
  --bg-raised: #ffffff;
  --bg-hi:     #eef1f4;

  --ink:   #10141a;
  --ink-2: #4d5661;
  --ink-3: #6f7883;
  --ink-4: #b6bec7;

  --rule:     #e2e6ea;
  --rule-hi:  #d3d9df;
  --rule-str: #b8c0c8;

  --glow: 16, 20, 26;

  /* 니어블랙에서 읽히는 채도가 니어화이트에서는 대비를 잃는다. 둘 다 어둡게. */
  --live: #197a6c;
  --mark: #8a5d05;
}

*, *::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);
  font-size: clamp(0.96rem, 0.9rem + 0.2vw, 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(--live);
  outline-offset: 2px;
  border-radius: 2px;
}

::selection { background: rgba(var(--glow), 0.18); }

/* 스크롤바도 사이트의 일부다. 코드 블록만 손보고 나머지를 기본값으로 두면
   스크롤이 생기는 자리마다 다른 물건이 나온다. */
* { scrollbar-width: thin; scrollbar-color: var(--rule-hi) transparent; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--rule-hi); border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-4); }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

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

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

.wrap {
  width: 100%;
  max-width: 68rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
}
/* 읽는 폭. 산문은 좁아야 눈이 줄을 놓치지 않는다. */
.wrap-read { max-width: 46rem; }
/* 원장은 날짜 거터가 왼쪽으로 나가 앉을 자리가 필요하다. */
.wrap-ledger { max-width: 54rem; }

main { display: block; padding-bottom: clamp(4rem, 10vw, 8rem); }

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

/* 높이를 고정한다. 내용에 따라 늘어나면 페이지마다 상단바가 달라지고, 그게
   "비율이 이상하다"로 읽힌다. */
.masthead {
  position: sticky;
  top: 0;
  z-index: 30;
  height: var(--masthead-h);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
}

.masthead-in {
  display: flex;
  align-items: center;
  gap: clamp(0.6rem, 2vw, 1.75rem);
  height: 100%;
  max-width: 68rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.brand {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  flex: none;
  font-family: var(--mono);
  font-weight: 700;
  letter-spacing: -0.03em;
}
/* 마크는 잉크로 그린다. 원본이 투명 배경의 검은 선화라 <img> 로 넣으면 다크
   배경에서 사라지는데, 마스크로 두면 배경색이 currentColor 가 되어 글자와 같이
   변한다. 원본 비율 240:131 을 박아 두어 로딩 중에도 자리가 안 움직인다. */
.brand-mark {
  width: 1.6em;
  aspect-ratio: 128 / 123;
  flex: none;
  align-self: center;
  background-color: var(--live);
  -webkit-mask: url("/images/mark.png") center / contain no-repeat;
  mask: url("/images/mark.png") center / contain no-repeat;
  transition: background-color var(--tr-ink);
}
.brand:hover .brand-mark { background-color: var(--ink); }
/* 마스크를 모르는 브라우저에서는 색만 칠한 네모가 남는다. 워드마크가 이미
   이름을 말하고 있으므로 마크를 지우는 편이 낫다. */
@supports not ((mask: url("")) or (-webkit-mask: url(""))) {
  .brand-mark { display: none; }
}
.brand-name { color: var(--ink); }

.nav {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 2vw, 1.5rem);
  margin-right: auto;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.nav::-webkit-scrollbar { display: none; }

.nav a {
  position: relative;
  flex: none;
  padding-block: 0.35rem;
  font-size: 0.86rem;
  color: var(--ink-2);
  transition: color var(--tr-ink);
}
.nav a:hover { color: var(--ink); }

/* 현재 위치는 밑줄이 아니라 짧은 눈금이다. 밑줄은 링크의 표시이고 이건
   상태의 표시라, 서로 달라야 한다. */
.nav a[aria-current] { color: var(--ink); }
.nav a[aria-current]::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--live);
}

.tools { display: flex; align-items: center; gap: 0.4rem; flex: none; }

/* 크롬 컨트롤은 전부 같은 치수다. 하나만 다르게 생기면 그 하나가 고장난
   것처럼 보인다. 터미널에서 모는 도구이므로 키캡처럼 다룬다. */
.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  height: var(--control-h);
  padding-inline: 0.55rem;
  border: 1px solid var(--rule-hi);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
  transition: color var(--tr-ink), border-color var(--tr-ink), background var(--tr-ink);
}
.chip:hover { color: var(--ink); border-color: var(--rule-str); background: var(--bg-raised); }
.chip-icon { width: var(--control-h); padding-inline: 0; }
.chip-lang { font-family: var(--mono); }
.chip kbd {
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--ink-3);
  border: 1px solid var(--rule-hi);
  border-radius: 3px;
  padding: 0.05rem 0.28rem;
}

/* 버튼은 상태가 아니라 동작을 말한다. 다크에서는 해가, 라이트에서는 달이 뜬다. */
.i-moon { display: none; }
:root[data-theme="light"] .i-sun { display: none; }
:root[data-theme="light"] .i-moon { display: inline; }

@media (max-width: 34rem) {
  .chip kbd { display: none; }
  .brand-name { display: none; }
}

/* ---------- type primitives ---------- */

/* 눈금 라벨. 본문보다 작고 mono 이고 넓게 벌어져 있다 — 시각 위계를 의미
   위계와 반대로 두면 작은 글자가 오히려 구조로 읽힌다. */
.eyebrow {
  margin: 0 0 0.75rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.page-head { padding-block: clamp(2.5rem, 6vw, 4.5rem) clamp(1.5rem, 3vw, 2.5rem); }

.page-title {
  margin: 0;
  font-size: clamp(2rem, 1.5rem + 2.2vw, 3.1rem);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.page-lede {
  margin: 0.9rem 0 0;
  max-width: 52ch;
  font-size: 1.02rem;
  color: var(--ink-2);
  text-wrap: pretty;
}

.page-meta {
  margin: 1.1rem 0 0;
  font-family: var(--mono);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

/* 소개의 머리. 초상과 제목이 나란히 서고, 좁아지면 초상이 위로 간다.
   align-items 가 end 인 이유는 제목의 베이스라인이 초상의 아래변에 맞아야
   둘이 같은 바닥을 딛고 선 것으로 보이기 때문이다. */
.about-head {
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2.25rem);
  align-items: end;
}
.about-intro { min-width: 0; }

.portrait { margin: 0; }
.portrait img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  /* 사진의 주황 배경이 이 페이지에서 제일 센 색이다. 쉬는 동안 한 걸음 물러나
     있다가 다가가면 제 색으로 돌아온다. */
  filter: saturate(0.75) brightness(0.94);
  transition: filter var(--tr-light);
}
.about-head:hover .portrait img { filter: none; }

@media (max-width: 34rem) {
  .about-head { grid-template-columns: minmax(0, 1fr); }
  .portrait { max-width: 9rem; }
}

.crumbs { margin-bottom: 0.9rem; font-family: var(--mono); font-size: 0.75rem; }
.crumbs a { color: var(--ink-3); transition: color var(--tr-ink); }
.crumbs a:hover { color: var(--live); }

/* ---------- plate: 섹션 머리 ---------- */

/* 라벨 + 오른쪽 끝까지 가는 실선 + 문(door) 링크. 라벨이 본문보다 작다. */
.plate {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: 1.5rem;
}

.plate-label {
  margin: 0;
  flex: none;
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-2);
}

/* 스크롤 리빌이 붙는 자리. 빛웅덩이가 아니라 선이 그어진다 — 커서나 스캔
   라인처럼 읽히고, 분위기가 아니라 구조를 말한다. */
.plate-rule {
  flex: 1 1 auto;
  height: 1px;
  background: var(--rule-hi);
  transform-origin: left center;
  transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}
.js [data-reveal] .plate-rule { transform: scaleX(0); }
.js [data-reveal].is-lit .plate-rule { transform: scaleX(1); }

.plate-count {
  flex: none;
  font-family: var(--mono);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

.plate-door {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.78rem;
  color: var(--ink-2);
  transition: color var(--tr-ink), gap var(--tr-light);
}
.plate-door:hover { color: var(--live); gap: 0.6rem; }

.group-note {
  margin: -0.6rem 0 1.4rem;
  max-width: 60ch;
  font-size: 0.9rem;
  color: var(--ink-3);
}

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

.hero-home { padding-block: clamp(3.5rem, 11vw, 8rem) clamp(2rem, 5vw, 4rem); }

.statement {
  margin: 0;
  max-width: 18ch;
  font-size: clamp(2.4rem, 1.4rem + 4.4vw, 5rem);
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: -0.04em;
  text-wrap: balance;
}

.statement-sub {
  margin: 1.4rem 0 0;
  max-width: 46ch;
  font-size: 1.05rem;
  color: var(--ink-2);
  text-wrap: pretty;
}

.home-block { padding-block: clamp(2.5rem, 6vw, 4.5rem); }

/* ---------- feature card (홈 맨 위 글) ---------- */

.feature {
  display: block;
  position: relative;
  padding: clamp(1.4rem, 3vw, 2.2rem);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--bg-raised);
  overflow: hidden;
  transition: border-color var(--tr-ink);
}
/* hover 는 채움이 아니라 빛이다. 왼쪽에서 스며 나오는 방향성 wash. */
.feature::before {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(100deg, rgba(var(--glow), 0.05), transparent 60%);
  opacity: 0;
  transition: opacity var(--tr-light);
  pointer-events: none;
}
.feature:hover, .feature:focus-visible { border-color: var(--rule-str); }
.feature:hover::before, .feature:focus-visible::before { opacity: 1; }

.feature-date {
  font-family: var(--mono);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  color: var(--mark);
}

.feature-title {
  margin: 0.5rem 0 0;
  font-size: clamp(1.4rem, 1.1rem + 1vw, 1.9rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.025em;
  text-wrap: balance;
}

.feature-desc {
  margin: 0.6rem 0 0;
  max-width: 58ch;
  color: var(--ink-2);
}

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

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

.ledger-year {
  margin-top: 2rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  color: var(--ink-4);
}
.ledger-year:first-child { margin-top: 0; }

.ledger-row { border-bottom: 1px solid var(--rule); }

/* 날짜 거터는 고정폭이고 baseline 으로 제목과 묶인다. 크기가 다른 두 글자를
   같은 기준선에 앉히는 것이 이 목록의 전부다. */
.ledger-link {
  position: relative;
  display: grid;
  grid-template-columns: var(--key-width) minmax(0, 1fr);
  align-items: baseline;
  gap: 1rem;
  padding: 0.95rem 0.75rem;
  margin-inline: -0.75rem;
  border-radius: var(--radius);
  transition: background var(--tr-light);
}
.ledger-link:hover, .ledger-link:focus-visible { background: rgba(var(--glow), 0.04); }

.ledger-date {
  font-family: var(--mono);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
  transition: color var(--tr-ink);
}
.ledger-link:hover .ledger-date { color: var(--mark); }

.ledger-body { display: block; min-width: 0; }

.ledger-title {
  display: block;
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--ink-2);
  transition: color var(--tr-ink);
  text-wrap: balance;
}
.ledger-link:hover .ledger-title { color: var(--ink); }

.ledger-desc {
  display: block;
  margin-top: 0.15rem;
  max-width: 58ch;
  font-size: 0.88rem;
  color: var(--ink-3);
}

@media (max-width: 34rem) {
  .ledger-link { grid-template-columns: 1fr; gap: 0.25rem; }
}

/* ---------- tags & dots ---------- */

.tag {
  display: inline-flex;
  align-items: center;
  height: 1.45rem;
  padding-inline: 0.45rem;
  border: 1px solid var(--rule-hi);
  border-radius: 3px;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  white-space: nowrap;
}
.tag-lang { color: var(--ink-2); }

.dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--live);
  flex: none;
}
.is-archived .dot { background: var(--ink-4); }

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

.btn-row { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.6rem; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  height: 2.4rem;
  padding-inline: 1rem;
  border: 1px solid var(--rule-hi);
  border-radius: var(--radius);
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--ink);
  transition: border-color var(--tr-ink), background var(--tr-ink), color var(--tr-ink), gap var(--tr-light);
}
/* 화살표가 라벨에서 멀어진다. transform 이 아니라 gap 을 움직이면 글자가
   제자리에 있고 간격만 열린다. */
.btn:hover { gap: 0.7rem; }
.btn .i-out, .btn .i-arrow { opacity: 0.55; }

.btn-primary {
  background: var(--live);
  border-color: var(--live);
  color: var(--bg);
  font-weight: 600;
}
.btn-primary:hover { filter: brightness(1.1); }

.btn-ghost:hover { border-color: var(--rule-str); background: var(--bg-raised); }

.btn-text { border-color: transparent; padding-inline: 0.3rem; color: var(--ink-2); }
.btn-text:hover { color: var(--live); }

.back {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 2rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--ink-3);
  transition: color var(--tr-ink), gap var(--tr-light);
}
.back:hover { color: var(--ink); gap: 0.6rem; }

/* ---------- bento (featured projects) ---------- */

/* flex 로 두면 셋이든 넷이든 마지막 줄에 하나만 남는 일이 없다. grid 의
   고정 열은 개수가 바뀌는 순간 그 문제를 만든다. */
.bento {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: clamp(3rem, 7vw, 5rem);
}

.bento-card {
  flex: 1 1 20rem;
  max-width: 34rem;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--bg-raised);
  overflow: hidden;
  transition: border-color var(--tr-ink);
}
.bento-card:hover { border-color: var(--rule-str); }

/* 커버가 없을 때 회색 상자를 놓지 않는다. 이름을 크게 깔고 뒤로 물리면
   그 자체가 그림이 된다. 나중에 이미지를 넣으면 이게 물러난다. */
.bento-media {
  position: relative;
  aspect-ratio: 16 / 7;
  display: grid;
  place-items: center;
  background: var(--bg-recess);
  border-bottom: 1px solid var(--rule);
  overflow: hidden;
}
.bento-media img { width: 100%; height: 100%; object-fit: cover; }

/* 선화 마크. 메달리온과 같은 방식이라 상세 페이지와 카드가 같은 그림을 같은
   규칙으로 그린다. */
.bento-mark {
  width: 46%;
  aspect-ratio: 1;
  background-color: var(--ink-4);
  -webkit-mask: var(--mark-src) center / contain no-repeat;
  mask: var(--mark-src) center / contain no-repeat;
  transition: background-color var(--tr-light);
}
.bento-card:hover .bento-mark { background-color: var(--ink-3); }

/* 그림이 아무것도 없을 때만 낮춘다. 이름 하나를 담자고 16:7 을 비워 두면 카드가
   비어 있는 것으로 읽힌다. 커버든 마크든 들어오면 원래 비율이고, 그래야 카드
   여러 장의 본문이 같은 높이에서 시작한다. */
.bento-media:not(:has(img)):not(:has(.bento-mark)) { aspect-ratio: 16 / 4.5; }

.bento-glyph {
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(2rem, 5vw, 3.2rem);
  letter-spacing: -0.05em;
  color: var(--ink-4);
  transition: color var(--tr-light);
}
.bento-card:hover .bento-glyph { color: var(--ink-3); }

.bento-body {
  padding: 1.1rem 1.2rem 1.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  flex: 1;
}

.bento-name { margin: 0; font-size: 1.15rem; font-weight: 700; letter-spacing: -0.02em; font-family: var(--mono); }
.bento-name a { transition: color var(--tr-ink); }
.bento-name a:hover { color: var(--live); }

.bento-blurb { margin: 0; flex: 1; max-width: 44ch; font-size: 0.9rem; color: var(--ink-2); }

.bento-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 0.35rem;
}
.bento-links { display: inline-flex; gap: 0.15rem; }
.bento-links a, .row-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.8rem; height: 1.8rem;
  border-radius: 3px;
  color: var(--ink-3);
  transition: color var(--tr-ink), background var(--tr-ink);
}
.bento-links a:hover, .row-links a:hover { color: var(--ink); background: var(--bg-hi); }

/* ---------- project rows ---------- */

.group { padding-block: clamp(1.75rem, 4vw, 3rem); }

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

.row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem 0.75rem;
  margin-inline: -0.75rem;
  border-bottom: 1px solid var(--rule);
  border-radius: var(--radius);
  transition: background var(--tr-light);
}
.row:hover { background: rgba(var(--glow), 0.04); }

/* 행 전체가 링크지만 오른쪽 아이콘들은 따로 눌려야 한다. 늘린 링크를 깔고
   상호작용하는 것들만 위로 올린다. */
.row-main { display: flex; align-items: baseline; gap: 0.85rem; flex: 1; min-width: 0; }
.row-main::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }

.row-name {
  flex: none;
  font-family: var(--mono);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: -0.02em;
  color: var(--ink-2);
  transition: color var(--tr-ink);
}
.row:hover .row-name { color: var(--ink); }

.row-blurb {
  min-width: 0;
  font-size: 0.88rem;
  color: var(--ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-meta, .row-links {
  position: relative;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
}

@media (max-width: 44rem) {
  .row { flex-wrap: wrap; }
  .row-main { flex-basis: 100%; flex-direction: column; align-items: flex-start; gap: 0.2rem; }
  .row-blurb { white-space: normal; }
}

/* ---------- project hero ---------- */

.project { padding-bottom: clamp(3rem, 8vw, 6rem); }

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 18rem;
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: center;
  padding-block: clamp(2rem, 5vw, 3.5rem) clamp(2rem, 5vw, 3rem);
}
/* 옆칸이 없으면 한 칸으로. 템플릿이 클래스를 따로 내려보내지 않아도 된다. */
.hero:not(:has(.hero-aside)) { grid-template-columns: minmax(0, 1fr); }

.hero-main { min-width: 0; }

.hero-name {
  margin: 0;
  font-family: var(--mono);
  font-size: clamp(2.2rem, 1.6rem + 3vw, 4rem);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.05em;
}

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

.badges { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 1.2rem 0 0; padding: 0; list-style: none; }

/* 로고 유무와 무관하게 같은 규격이다. 나중에 SVG 를 넣어도 레이아웃이 안 움직인다. */
.hero-aside { display: grid; place-items: center; }
.medallion {
  display: grid;
  place-items: center;
  width: min(100%, 15rem);
  aspect-ratio: 1;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--bg-raised);
  overflow: hidden;
}
.medallion img { width: 76%; height: 76%; object-fit: contain; }

/* 선화 마크. 인라인으로 오는 것은 --mark-src 하나뿐이고 그건 장식이 아니라
   데이터다 — 어떤 파일인지는 프로젝트마다 다르고, 어떻게 그릴지는 전부 여기 있다.
   currentColor 를 타므로 테마를 따라가고, 원본이 무슨 색이든 페이지와 싸우지 않는다. */
.medallion-mark::before {
  content: "";
  width: 76%;
  height: 76%;
  background-color: var(--ink-3);
  -webkit-mask: var(--mark-src) center / contain no-repeat;
  mask: var(--mark-src) center / contain no-repeat;
  transition: background-color var(--tr-light);
}
.hero:hover .medallion-mark::before { background-color: var(--ink-2); }

.medallion-glyph {
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(1.5rem, 4vw, 2.4rem);
  letter-spacing: -0.05em;
  color: var(--ink-4);
}

@media (max-width: 48rem) {
  .hero { grid-template-columns: minmax(0, 1fr); }
  .hero-aside { display: none; }
}

/* 브리프는 읽는 폭 안에 산다. 거기에 옆칸까지 넣으면 태그라인이 좁은 기둥으로
   눌리고, 그 자리를 채우는 것은 로고도 아니고 이름을 흐리게 깐 사각형이다. */
.project[data-tier="brief"] .hero { grid-template-columns: minmax(0, 1fr); }
.project[data-tier="brief"] .hero-aside { display: none; }

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

/* 별 개수가 있던 자리. 확인할 수 있는 사실만 들어간다. */
.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1rem;
  margin-block: clamp(1.5rem, 4vw, 2.5rem);
  padding-block: 1.4rem;
  border-block: 1px solid var(--rule);
}

.fact { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.fact-value {
  font-family: var(--mono);
  font-size: clamp(1.3rem, 1.1rem + 0.8vw, 1.7rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}
.fact-label {
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ---------- blocks & lattice ---------- */

.block { padding-block: clamp(2.5rem, 6vw, 4.5rem); }
.block-head { margin-bottom: 1.8rem; }
.block-title {
  margin: 0;
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2.1rem);
  font-weight: 700;
  letter-spacing: -0.028em;
  text-wrap: balance;
}
.block-intro { margin: 0.7rem 0 0; max-width: 60ch; color: var(--ink-2); }

/* 선은 음수 마진으로 겹친 1px 이다. 개수와 열수가 무엇이든 맞아 들어가고
   :nth-child 규칙이 필요 없다. hover 에서 z-index 를 올리는 이유는, 그러지
   않으면 이웃에 가려 두 변만 밝아지기 때문이다. */
/* 18rem 은 임의의 값이 아니다. 이 폭에서 열이 3개가 되고, 특징을 여섯 개로
   쓰면 두 줄이 정확히 찬다. 열이 4개면 마지막 줄에 빈 칸 두 개가 테두리만
   남기고 서 있는데, 그건 격자가 아니라 덜 채워진 표로 읽힌다. */
.lattice {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
}

.cell {
  position: relative;
  margin: 0 -1px -1px 0;
  padding: 1.3rem 1.4rem 1.5rem;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  min-width: 0;
  transition: background var(--tr-light), border-color var(--tr-ink);
}
.cell:hover {
  z-index: 1;
  background: rgba(var(--glow), 0.035);
  border-color: var(--rule-str);
}

.cell-title {
  margin: 0;
  font-size: 0.98rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  transition: color var(--tr-ink);
}
.cell-desc { margin: 0.45rem 0 0; font-size: 0.88rem; color: var(--ink-3); text-wrap: pretty; }

.cell-link { display: block; }
.cell-link:hover .cell-title { color: var(--live); }

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

.term {
  border: 1px solid var(--rule-hi);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-recess);
}

.term-bar {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.5rem 0.8rem;
  border-bottom: 1px solid var(--rule);
  background: var(--bg-raised);
}
.term-dots { display: inline-flex; gap: 0.32rem; }
.term-dots i { width: 8px; height: 8px; border-radius: 50%; background: var(--rule-str); }
.term-dots i:first-child { background: var(--ink-4); }
.term-name { font-family: var(--mono); font-size: 0.7rem; color: var(--ink-3); }

/* 배경이 페이지보다 어둡다 — 페이지 위에 얹힌 판이 아니라 페이지에 뚫린
   구멍처럼 읽혀야 한다. */
.term-body {
  margin: 0;
  padding: 1.1rem 1.2rem;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 0.82rem;
  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-3); }
.term-out { color: var(--live); }

.term-caption { margin: 0.7rem 0 0; font-size: 0.78rem; color: var(--ink-3); }

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

.cta {
  margin-top: clamp(2rem, 5vw, 3.5rem);
  padding: clamp(1.8rem, 4vw, 3rem);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--bg-raised);
}
.cta-title {
  margin: 0;
  font-size: clamp(1.4rem, 1.1rem + 1.2vw, 2rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  text-wrap: balance;
}
.cta-text { margin: 0.7rem 0 0; max-width: 52ch; color: var(--ink-2); }
.cta .btn-row { margin-top: 1.4rem; }

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

.toc {
  margin: 2rem 0 0;
  padding: 1rem 1.2rem;
  border-left: 2px solid var(--rule-hi);
  background: var(--bg-raised);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.toc-label {
  margin: 0 0 0.5rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.toc ul { list-style: none; margin: 0; padding: 0; }
.toc ul ul { padding-left: 1rem; }
.toc li { margin: 0.25rem 0; font-size: 0.88rem; }
.toc a { color: var(--ink-2); transition: color var(--tr-ink); }
.toc a:hover { color: var(--live); }

.pager {
  display: flex;
  gap: 0.8rem;
  margin-top: clamp(3rem, 7vw, 5rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
}
.pager-link {
  flex: 1;
  padding: 0.9rem 1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  transition: border-color var(--tr-ink), background var(--tr-light);
}
.pager-link:hover { border-color: var(--rule-str); background: var(--bg-raised); }
/* 하나만 있을 때 "다음"은 오른쪽에 앉는다 — 읽는 사람이 가는 방향이다. */
.pager-next { text-align: right; }
.pager-next:only-child { margin-left: auto; flex: 0 1 22rem; }
.pager-dir {
  display: block;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.2rem;
}
.pager-title { display: block; font-size: 0.92rem; font-weight: 600; color: var(--ink-2); }
.pager-link:hover .pager-title { color: var(--ink); }

@media (max-width: 40rem) { .pager { flex-direction: column; } }

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

.prose { font-size: 1.02rem; line-height: 1.78; }
.prose-intro { margin-bottom: 2rem; color: var(--ink-2); }

/* 랜딩과 목록 페이지는 넓은 wrap 을 쓰지만 산문은 거기 따라가면 안 된다.
   한 줄이 68rem 이면 눈이 다음 줄의 첫 글자를 못 찾는다. */
.project .prose, .prose-intro { max-width: 44rem; }

/* 여백은 형제 사이에만 둔다. 초기화를 `.prose p` 같은 타입 셀렉터로 쓰면
   특정성이 (0,1,1) 이라 (0,1,0) 인 아래 규칙을 이겨 버리고, 문단 사이 여백이
   통째로 사라진다. 둘 다 (0,1,0) 으로 맞추고 순서로 결정한다. */
.prose > * { margin: 0; }
.prose > * + * { margin-top: 1.15rem; }
.prose p { text-wrap: pretty; }

.prose h2, .prose h3, .prose h4 {
  margin: 2.6rem 0 0;
  line-height: 1.2;
  letter-spacing: -0.025em;
  text-wrap: balance;
  scroll-margin-top: calc(var(--masthead-h) + 1rem);
}
.prose h2 { font-size: clamp(1.35rem, 1.15rem + 0.8vw, 1.7rem); font-weight: 700; }
.prose h3 { font-size: 1.15rem; font-weight: 600; }
.prose h4 { font-size: 1rem; font-weight: 600; }
.prose h2 + *, .prose h3 + *, .prose h4 + * { margin-top: 0.75rem; }

.prose a { color: var(--live); text-decoration: underline; text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
.prose a:hover { text-decoration-thickness: 2px; }

.prose ol { margin: 1.15rem 0 0; padding-left: 1.35rem; }
.prose ul { margin: 1.15rem 0 0; list-style: none; padding-left: 1.1rem; }
.prose li { margin: 0.35rem 0; }
/* 불릿 대신 짧은 선. 점보다 조용하고, mono 눈금과 같은 어휘다. */
.prose ul > li { position: relative; }
.prose ul > li::before {
  content: "";
  position: absolute;
  left: -1.1rem; top: 0.85em;
  width: 0.5rem; height: 1px;
  background: var(--ink-4);
}

.prose blockquote {
  margin: 1.8rem 0 0;
  padding-left: 1.2rem;
  border-left: 2px solid var(--rule-str);
  color: var(--ink-2);
}

.prose code {
  font-family: var(--mono);
  font-size: 0.86em;
  padding: 0.12em 0.35em;
  border-radius: 3px;
  background: var(--bg-hi);
}

.prose pre {
  margin: 1.5rem 0 0;
  padding: 1rem 1.1rem;
  overflow-x: auto;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--bg-recess);
  font-size: 0.84rem;
  line-height: 1.7;
}
.prose pre code { padding: 0; background: none; font-size: inherit; }

/* 복사 버튼은 pre 안에 앉는다. 래퍼를 새로 만들면 .prose 의 형제 간격 규칙이
   pre 가 아니라 래퍼에 걸려 위아래 여백이 어긋난다.
   hover 만이 아니라 focus-within 에서도 나온다 — 키보드로는 hover 할 수 없다. */
.prose pre { position: relative; }
.copy-btn {
  position: absolute;
  top: 0.45rem;
  right: 0.45rem;
  padding: 0.15rem 0.45rem;
  border: 1px solid var(--rule-hi);
  border-radius: 3px;
  background: var(--bg-raised);
  color: var(--ink-3);
  font-family: var(--mono);
  font-size: 0.68rem;
  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-str); }
.copy-btn.is-done { opacity: 1; color: var(--live); border-color: var(--live); }

.prose table {
  width: 100%;
  margin: 1.6rem 0 0;
  border-collapse: collapse;
  font-size: 0.92rem;
  font-variant-numeric: tabular-nums;
}
.prose th, .prose td { padding: 0.5rem 0.7rem; text-align: left; border-bottom: 1px solid var(--rule); }
.prose th {
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  border-bottom-color: var(--rule-hi);
}
.prose td { color: var(--ink-2); }

.prose hr { margin: 2.5rem 0 0; 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-hi);
  border-radius: var(--radius);
  background: var(--bg-raised);
  color: var(--ink);
}
.palette::backdrop { background: rgba(3, 5, 8, 0.62); backdrop-filter: blur(3px); }

.palette-form { margin: 0; }

.palette-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 0.8rem;
  border-bottom: 1px solid var(--rule);
  color: var(--ink-3);
}

.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-3); }
.palette-close kbd {
  font-family: var(--mono);
  font-size: 0.66rem;
  border: 1px solid var(--rule-hi);
  border-radius: 3px;
  padding: 0.1rem 0.3rem;
}

.palette-results { list-style: none; margin: 0; padding: 0.35rem; max-height: 56vh; overflow-y: auto; }
.palette-results:empty { display: none; }

.palette-results a { display: block; padding: 0.55rem 0.65rem; border-radius: 3px; }
.palette-results li[aria-selected="true"] a,
.palette-results a:hover { background: var(--bg-hi); }

.palette-title { display: block; font-size: 0.92rem; font-weight: 600; }
.palette-crumb {
  display: block;
  margin-top: 0.1rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--ink-3);
}
.palette-results mark { background: none; color: var(--mark); font-weight: 700; }

.palette-status {
  margin: 0;
  padding: 0.5rem 0.8rem;
  border-top: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--ink-3);
}
.palette-status:empty { display: none; }

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

.colophon { border-top: 1px solid var(--rule); padding-block: 2.5rem 3.5rem; }
.colophon-in {
  max-width: 68rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.colophon-nav { display: flex; flex-wrap: wrap; gap: 1.1rem; }
.colophon-nav a { font-size: 0.82rem; color: var(--ink-3); transition: color var(--tr-ink); }
.colophon-nav a:hover { color: var(--ink); }
.colophon-note { margin: 0; font-family: var(--mono); font-size: 0.75rem; color: var(--ink-4); }
.colophon-note a { color: var(--ink-3); transition: color var(--tr-ink); }
.colophon-note a:hover { color: var(--live); }
.colophon-sep { padding-inline: 0.4rem; }

/* ---------- scroll reveal ---------- */

/* 분위기가 아니라 구조를 드러낸다. 살짝 올라오면서 섹션의 선이 그어진다.
   숨기는 쪽을 .js 뒤에 두는 이유는, 스크립트가 없으면 아무것도 숨기지 않기
   위해서다 — CSS 로 먼저 숨기면 JS 가 죽었을 때 페이지가 통째로 사라진다. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(0.6rem);
  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.js [data-reveal].is-lit { opacity: 1; transform: none; }

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

/* 한국어는 라틴 타이포 장치 셋을 깨뜨린다. letter-spacing 은 음절 블록을
   벌려 놓고, text-transform 은 아무 일도 하지 않으며, monospace 가 가장
   나쁘다 — 한글은 프로포셔널 폰트로 폴백하는데 공백은 여전히 mono 폭이라
   어절 사이가 전부 두 배로 벌어진다.
   구조는 크기와 굵기와 색으로도 읽히고, 그쪽은 도움이 필요 없다. */
html[lang="ko"] .eyebrow,
html[lang="ko"] .plate-label,
html[lang="ko"] .fact-label,
html[lang="ko"] .toc-label,
html[lang="ko"] .pager-dir,
html[lang="ko"] .prose th,
html[lang="ko"] .crumbs,
html[lang="ko"] .palette-crumb,
html[lang="ko"] .colophon-note,
html[lang="ko"] .tag {
  font-family: var(--sans);
  letter-spacing: 0;
  text-transform: none;
}

html[lang="ko"] .statement,
html[lang="ko"] .page-title,
html[lang="ko"] .hero-name,
html[lang="ko"] .block-title,
html[lang="ko"] .cta-title,
html[lang="ko"] .feature-title,
html[lang="ko"] .bento-name {
  font-family: var(--sans);
  letter-spacing: -0.02em;
  font-weight: 700;
}

/* 날짜와 숫자는 한국어 페이지에서도 mono 로 둔다. 숫자에는 위 문제가 없고,
   자릿수가 맞아야 목록이 줄을 이룬다. */
html[lang="ko"] .ledger-date,
html[lang="ko"] .feature-date,
html[lang="ko"] .fact-value,
html[lang="ko"] .plate-count,
html[lang="ko"] .brand { 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 [data-reveal] { opacity: 1; transform: none; }
  .js [data-reveal] .plate-rule { transform: scaleX(1); }
}
