@charset "utf-8";
/* ==========================================================================
   layout.css — 컨테이너 · 섹션 · 그리드 · 헤더 · 푸터
   --------------------------------------------------------------------------
   base.css 다음에 로드된다. 여기서는 색·크기 값을 직접 쓰지 않고
   base.css 의 토큰(var(--...))만 참조한다.
   ========================================================================== */


/* ==========================================================================
   1. 컨테이너 · 섹션
   ========================================================================== */

/* 콘텐츠 최대 폭 + 좌우 안전 여백.
   margin-inline:auto 로 가운데 정렬하고, padding 으로 화면 가장자리와 띄운다.
   width:100% 없이 max-width 만 주면 되는 이유는 block 요소가 기본으로
   부모 폭을 채우기 때문이다. */
.l-container {
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.l-container--wide { max-width: var(--container-wide); }

/* 섹션 상하 리듬. 레퍼런스의 "라벨 → 큰 문장 → 콘텐츠" 3단 구조를 담는 그릇 */
.l-section {
  padding-block: var(--sp-section);
}

/*
 * 앵커 도착 위치 — 섹션은 화면 맨 위(y=0)에서 시작하게 한다.
 *
 * 헤더는 투명하게 떠 있는 구조라, 섹션이 헤더 뒤로 들어가야 인트로 첫 화면처럼
 * "캡슐 위쪽에도 목적지 섹션 배경만 보이는" 상태가 된다.
 * 헤더 높이만큼 아래에 세우면 그 위 구간에 이전 섹션이 비친다.
 *
 * 왜 0 이 아니라 음수인가:
 * 브라우저는 앵커로 이동할 때 두 값을 "더해서" 위치를 잡는다.
 *   최종 위치 = html 의 scroll-padding-top  +  목적지의 scroll-margin-top
 * base.css 가 두 값을 모두 --anchor-offset(90~106px)으로 걸어 두었으므로,
 * 여기서 같은 크기의 음수를 줘야 서로 상쇄되어 정확히 0 이 된다.
 * (scroll-padding-top 은 게시판 앵커 등 다른 목적지에 여전히 필요하므로 지우지 않는다)
 *
 * 라벨이 헤더에 가리지 않는 이유는 섹션 자체의 위쪽 안쪽 여백(--sp-section,
 * 88~176px)이 헤더 높이(74px)보다 크기 때문이다. 이 여백을 74px 아래로 줄이면
 * 라벨이 헤더에 가려지므로, 줄일 때는 이 관계를 함께 확인해야 한다.
 */
.l-section[id] {
  scroll-margin-top: calc(-1 * var(--anchor-offset));
}

/* 배경색이 다른 섹션이 연달아 올 때 경계선을 겹치지 않게 */
.l-section + .l-section {
  border-top: 1px solid var(--color-border);
}

.l-section--sunken { background-color: var(--color-bg-sunken); }

/* 섹션 머리말 : 라벨 + 큰 문장 */
/* 섹션 머리말과 콘텐츠 사이 간격도 함께 키운다.
   섹션 상하 여백만 키우고 이 값을 그대로 두면, 제목과 콘텐츠가 한 덩어리로 뭉쳐
   "섹션 사이는 넓은데 섹션 안은 답답한" 상태가 된다. (40px → 80px) */
.l-section__head {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  margin-bottom: clamp(2.5rem, 1.5rem + 3vw, 5rem);
}

.l-section__title {
  font-size: var(--fs-h2);
  font-weight: var(--fw-regular);
  letter-spacing: -0.03em;
  max-width: 22ch;   /* 큰 글씨는 한 줄이 너무 길면 눈이 되돌아오지 못한다 */
}

/*
 * 가로형 머리말 — 라벨 ｜ 제목 ｜ 보조 링크 를 한 줄에 놓는다(시안 작업 섹션).
 * baseline 정렬을 쓰는 이유: 글자 크기가 제각각(11px / 46px / 11px)이라
 * center 로 맞추면 작은 글자가 붕 떠 보인다. 글자 밑선을 맞춰야 한 줄로 읽힌다.
 */
@media (min-width: 1024px) {
  .l-section__head--row {
    flex-direction: row;
    align-items: baseline;
    gap: 40px;
  }

  .l-section__head--row .l-section__title {
    flex: 1;          /* 남는 폭을 제목이 가져가 좌우 링크를 양끝으로 민다 */
    max-width: none;
  }
}

/* 머리말 우측 보조 링크 */
.section-link {
  white-space: nowrap;
  color: var(--color-text-sub);
}

.section-link:hover {
  color: var(--color-accent);
  text-decoration: none;
}


/* ==========================================================================
   2. 그리드
   --grid-cols 는 base.css 에서 브레이크포인트마다 4 → 8 → 12 로 바뀐다.
   여기서는 그 값을 그대로 받아 쓰므로 미디어쿼리를 다시 쓸 필요가 없다.
   ========================================================================== */

.l-grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  gap: var(--grid-gap);
}

/* minmax(0, 1fr) 를 쓰는 이유:
   기본값 1fr 은 min-width:auto 라서 안의 긴 텍스트/이미지가 칸을 밀어내
   그리드가 화면 밖으로 삐져나간다. 0 으로 눌러야 칸이 제 폭을 지킨다. */

/* 칼럼 점유 유틸 — 비대칭 그리드용. 모바일에서는 전부 한 줄 차지. */
.l-col-4,
.l-col-6,
.l-col-7,
.l-col-8,
.l-col-12 { grid-column: span var(--grid-cols); }

@media (min-width: 768px) {
  .l-col-4  { grid-column: span 4; }
  .l-col-6  { grid-column: span 4; }  /* 태블릿(8칸)에서는 절반 */
  .l-col-7  { grid-column: span 8; }
  .l-col-8  { grid-column: span 8; }
}

@media (min-width: 1024px) {
  .l-col-4  { grid-column: span 4; }
  .l-col-6  { grid-column: span 6; }
  .l-col-7  { grid-column: span 7; }
  .l-col-8  { grid-column: span 8; }
}

/* 격자 배경선(레퍼런스: Chemistry).
   요소를 추가하지 않고 repeating-linear-gradient 로 세로선을 그린다.
   배경이므로 DOM 이 늘어나지 않고 스크린리더에도 잡히지 않는다. */
.l-gridlines {
  background-image: repeating-linear-gradient(
    to right,
    var(--color-border) 0,
    var(--color-border) var(--grid-line-w),
    transparent var(--grid-line-w),
    transparent calc(100% / var(--grid-cols))
  );
  background-position: left top;
}


/* ==========================================================================
   3. 헤더 · 네비게이션
   ========================================================================== */

/* 수축 감지용 1px 요소. 헤더 바로 위에 두고, 이게 화면 밖으로 나가면 수축한다. */
.nav-sentinel {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 1px;
  pointer-events: none;
}

/*
 * position: fixed 로 문서 흐름에서 완전히 빼낸다.
 *
 * sticky 였을 때는 헤더가 흐름 안에서 자기 높이만큼 자리를 차지해,
 * 인트로가 그 아래에서 시작 → "별도의 띠"처럼 보였다.
 * fixed 는 자리를 차지하지 않으므로 인트로가 화면 최상단부터 그려지고
 * 헤더는 그 위에 겹쳐 뜬다. 배경을 transparent 로 두면 인트로 그라디언트가
 * 헤더 뒤로 그대로 비친다.
 *
 * 대신 헤더가 자리를 비우므로, 인트로가 없는 페이지는 본문이 헤더 밑에 깔린다.
 * → --header-h 만큼 여백을 직접 확보한다(아래 .page-head 참고).
 *
 * 부수 효과 하나 더: 드롭다운도 흐름 밖에 있게 되어, 펼쳐도 본문을 밀어내지 않는다.
 * 즉 열고 닫는 동작이 페이지 리플로우를 아예 일으키지 않는다.
 */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-sticky);

  /* 헤더 박스는 화면 전체 폭을 덮는다. 바가 없는 좌우 여백과 아래쪽 패딩까지
     클릭을 가로채면 그 아래 인트로의 링크를 누를 수 없다.
     껍데기는 클릭을 통과시키고, 실제 UI(바·드롭다운)만 다시 받게 한다. */
  pointer-events: none;

  /* ---- 모바일 기본값 (~767px) : 캡슐을 쓰지 않는다 ----------------------
   * 작은 화면에서는 캡슐 전환이 득이 없다.
   * 수축 목표 폭이 380px 인데 휴대폰 화면이 이미 그 언저리라 폭이 거의
   * 줄지 않는다. 모양만 바뀌고 얻는 것이 없으면서, 스크롤할 때마다
   * 화면 맨 위에서 형태가 달라져 시선을 뺏는다.
   * 그래서 화면 끝까지 붙는 납작한 띠로 두고, 스크롤하면 표면만 켠다.
   *
   * 캡슐로 되돌리는 값은 아래 @media (min-width: 768px) 에 모아 두었다.
   * 값 자체를 토큰으로 바꾸므로 바와 드롭다운이 함께 따라온다.
   * ---------------------------------------------------------------------- */
  padding: 0;                       /* 화면 좌우 끝까지 */
  --bar-max: none;                  /* 수축 없음 */
  --bar-row-pad: var(--gutter);     /* 바 안쪽에서 본문과 같은 좌우 여백을 만든다 */
  /* 0 이 아니라 0px 로 적는다 — 단위가 반드시 있어야 한다.
     ::before 가 이 값을 calc(... + 1px) 로 받는데, 단위 없는 0 은 <number> 라
     <length> 와 더할 수 없다. calc(0 + 1px) 는 무효가 되고, 무효한 값이 하나라도
     섞이면 border-radius 선언 전체가 버려져 네 모서리가 모두 0 이 된다.
     (실제로 이 단위 하나 때문에 유리 표면이 통째로 각졌다) */
  --bar-radius: 0px;                /* 각진 띠 */

  --bar-h: 3.875rem;                /* 62px — 시안 바 높이. 아래 min-height 주석 참고 */

  /* ---- 모서리 곡률은 여기서만 정한다 -------------------------------------
   * 이 프로젝트에서 헤더 곡률이 세 번 어긋났고, 세 번 다 원인이 같았다.
   * "어느 모서리를 얼마나 굴릴지"를 각 요소가 따로 계산하고 있었던 것이다.
   * 상태(.is-open)나 소비자(::before)가 하나 늘 때마다 그 계산을 전부 찾아
   * 고쳐야 했고, 하나를 빠뜨리면 조용히 어긋났다.
   *
   *   - ::before 를 도입할 때 아래를 펴는 규칙을 안 옮겨서 표면만 둥글게 남았다
   *   - border-radius: inherit 를 쓰다가 상자 크기 차이(1px)를 놓쳤다
   *
   * 그래서 규칙을 뒤집는다. 상태는 아래 두 토큰만 바꾸고,
   * 소비자(바 · ::before · 드롭다운)는 토큰을 읽기만 한다.
   * 새 상태가 생기면 토큰만 건드리면 되고, 새 소비자가 생기면 읽기만 하면 된다.
   * ---------------------------------------------------------------------- */
  --bar-radius-top: var(--bar-radius);
  --bar-radius-bottom: var(--bar-radius);

  /*
   * 수축 전환에 관여하는 모든 속성이 쓰는 단 하나의 시간.
   *
   * 값은 시안이 명시한 0.55s(--dur-slow)다. 헤더 안에서 이 값을 참조하지 않는
   * 전환은 없으므로, 체감이 굼뜨면 여기 한 줄만 --dur-base(0.28s)로 바꾸면
   * 캡슐·배경·부제·Menu 글자가 한꺼번에 같이 빨라진다.
   * (속성마다 따로 고치면 예전처럼 끝나는 시점이 갈려 단계로 보인다)
   */
  --dur-shrink: var(--dur-slow);

  /*
   * 드롭다운 여닫는 속도. 캡슐 수축(--dur-shrink)과 따로 두는 이유가 있다.
   *
   * 이음매에서 세 가지가 동시에 움직인다 — 바의 아래 모서리, 유리 표면의
   * 아래 모서리, 그리고 패널의 등장. 이 셋이 같은 시간에 끝나야 "캡슐 아래가
   * 펴지면서 패널이 이어져 나온다"로 읽힌다.
   *
   * 예전에는 셋이 제각각이었다.
   *   패널 등장        280ms
   *   바 모서리        550ms  (수축 시간을 같이 쓰고 있었다)
   *   표면(::before)   0ms    (전환 목록에 아예 없었다)
   * 표면이 먼저 툭 각지고, 패널이 오고, 테두리가 뒤늦게 따라와 이음매가 흔들렸다.
   *
   * 값은 패널 등장 시간(--dur-base)에 맞춘다. 여닫는 동작은 빨라야 하고,
   * 이미 자연스럽던 쪽이 그쪽이다.
   */
  --dur-open: var(--dur-base);
}

.site-header__bar {
  pointer-events: auto;   /* 껍데기가 통과시킨 클릭을 여기서 다시 받는다 */
  position: relative;     /* 아래 ::before(유리 표면)의 배치 기준 */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  margin-inline: auto;
  padding: var(--sp-3) var(--bar-row-pad);

  /*
   * 테두리는 바 자신이 그린다(색만 상태에 따라 바뀐다).
   * 배경·블러·그림자는 아래 ::before 가 맡는다 — 이유는 그쪽 주석 참고.
   * 항상 자리를 차지하게 두는 이유는 레이아웃이다.
   * 상태에 따라 테두리가 생겼다 없어지면 바 크기가 그만큼 튄다.
   *
   * 모바일에서는 아래쪽만 그린다. 바가 화면 끝까지 붙으므로 좌우 테두리는
   * 화면 가장자리에 걸린 실선이 되고, 위쪽 테두리는 화면 맨 윗줄에 얹혀
   * 렌더링 오류처럼 보인다. 실제로 쓸모 있는 것은 본문과의 경계인 아래쪽뿐이다.
   * (768px 부터는 캡슐이므로 네 변을 모두 그린다 — 아래 미디어쿼리)
   */
  border: 0 solid transparent;
  border-bottom-width: 1px;
  background-color: transparent;

  /*
   * 높이를 62px 로 고정한다. 캡슐 모양은 "모서리 반지름 = 높이의 절반"일 때만
   * 정확한 알약이 되므로, 높이가 흔들리면 반지름도 같이 어긋난다.
   *
   * ※ min-height 는 "내용이 이 값보다 작을 때만" 구속력을 갖는다.
   *   이전에 48px 로 잡았을 때는 내용 높이가 이미 68px 이라 한 번도 적용되지 않았고,
   *   그래서 반지름(24px)이 실제 높이의 절반(34px)과 어긋나 알약이 되지 않았다.
   *   아래 line-height 를 조여 내용을 62px 아래로 내려야 이 값이 실제로 먹는다.
   */
  min-height: var(--bar-h);

  /*
   * 수축 애니메이션은 width 가 아니라 max-width 로 한다.
   * width 를 애니메이션하면 매 프레임 레이아웃(reflow)이 다시 계산되어
   * 자식 요소 위치까지 전부 재배치된다 → 프레임 드랍.
   * max-width 는 컨테이너 크기만 조이므로 비용이 훨씬 싸다.
   */
  max-width: var(--bar-max);

  /* 위/아래 토큰을 읽기만 한다. 열림 상태에서 아래를 펴는 일은 토큰이 맡는다. */
  border-radius:
    var(--bar-radius-top) var(--bar-radius-top)
    var(--bar-radius-bottom) var(--bar-radius-bottom);

  /*
   * 전환 시간을 전부 --dur-shrink 하나로 맞춘다(위 .site-header 에서 정의).
   *
   * 예전에는 속성마다 시간이 달랐다.
   *   backdrop-filter  전환 목록에 없음 → 0ms (즉시)
   *   border-radius    160ms
   *   background-color / border-color / box-shadow  280ms
   *   max-width / padding                            550ms
   *
   * 끝나는 시점이 네 번으로 갈리니 "한 동작"이 아니라 단계로 보인다.
   * 데스크톱은 프레임이 안 밀려서 겹쳐 보였을 뿐이고, iOS 사파리처럼
   * 프레임이 몇 장 빠지는 환경에서는 그 간격이 그대로 드러난다.
   * (실제 증상: 완전투명 → 블러만 먼저 → 한참 뒤 캡슐)
   *
   * 값은 0.55s 로 통일했다. 시안이 명시한 유일한 전환 시간이 padding 의
   * 0.55s 이므로, 시안 값을 낮추는 대신 나머지를 거기에 맞췄다.
   */
  transition:
    max-width     var(--dur-shrink) var(--ease-out),
    padding       var(--dur-shrink) var(--ease-out),
    border-color  var(--dur-shrink) var(--ease-out),

    /* 모서리만 --dur-open 을 쓴다. 이 값이 바뀌는 이유는 두 가지(수축·여닫기)인데,
       수축 때는 20px -> 31px 로 변화가 작아 빨리 끝나도 티가 안 나는 반면,
       여닫을 때는 패널과 이음매를 맞춰야 해서 늦으면 바로 드러난다. */
    border-radius var(--dur-open) var(--ease-out);
}

/*
 * 유리 표면(반투명 배경 + 블러 + 그림자)을 바 자신이 아니라 ::before 에 그린다.
 * --------------------------------------------------------------------------
 * 왜 굳이 분리했나 — backdrop-filter 를 전환 대상으로 삼지 않기 위해서다.
 *
 * 예전 구조는 .is-shrunk 가 붙는 순간 backdrop-filter 를 blur(0) 없이
 * 곧바로 blur(12px) 로 켰다. 전환 목록에 없으니 0ms, 즉 한 프레임에 튄다.
 * 그렇다고 전환 목록에 넣으면 이번에는 블러 반지름이 매 프레임 바뀐다.
 * 블러는 "뒤 배경을 다시 샘플링해서 흐리는" 연산이라 반지름이 바뀔 때마다
 * 처음부터 다시 계산한다. 게다가 이 바는 시작할 때 1440px 폭이라
 * 그 넓은 띠를 0.55초 내내 다시 흐리게 된다. iOS 사파리에서 스크롤이
 * 통째로 끊기던 지점이 여기다.
 *
 * 그래서 블러 반지름은 고정해 두고 opacity 만 0 → 1 로 올린다.
 * opacity 는 레이아웃도 페인트도 다시 하지 않고 합성(composite) 단계에서만
 * 처리되는 속성이라, 어느 기기에서든 값이 싸다.
 *
 * 최상단에서는 backdrop-filter 를 none 으로 꺼 둔다. 켜지는 순간은
 * opacity 가 아직 0 인 시점이라 눈에 보이지 않는다. 꺼 두는 이유는
 * opacity:0 이어도 브라우저가 블러를 계산하는 경우가 있어서다.
 *
 * z-index: -1 — 바의 테두리와 글자보다 뒤에 깔린다.
 * inset: -1px — 테두리 자리까지 덮는다. 0 으로 두면 테두리 안쪽까지만
 *   배경이 깔려 테두리와 배경 사이로 뒤 화면이 1px 비친다.
 *   테두리가 위에 그려지므로 삐져나온 1px 은 가려진다.
 */
.site-header__bar::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;

  /*
   * 반지름을 inherit 로 두면 안 된다.
   *
   * inset:-1px 이라 이 상자는 바보다 사방으로 1px 씩 크다.
   * 캡슐은 "반지름 = 높이의 절반" 일 때만 정확한 알약이 되는데,
   * 바(62px)에 맞춘 31px 을 64px 짜리 이 상자에 그대로 쓰면 1px 이 모자라
   * 좌우 끝에 납작한 구간이 생긴다. 테두리는 62px 기준으로 동그랗게 그려지므로
   * 그 납작한 배경만 테두리 밖으로 삐져나와 캡슐이 뭉뚝해 보였다.
   * (배포 후 모바일에서 "많이 뭉뚝해졌다"로 드러난 지점)
   *
   * 1px 을 더해 두 상자의 곡률을 맞춘다. 최상단(20px)에서도 21px 이 되어 정확하다.
   *
   * 바와 같은 토큰을 읽되 상자가 1px 씩 크므로 각 모서리에 1px 을 더한다.
   * 여기서 "어느 모서리를 펼지"를 다시 판단하지 않는 것이 핵심이다 —
   * 그 판단은 토큰 한 곳에만 있다.
   */
  border-radius:
    calc(var(--bar-radius-top) + 1px) calc(var(--bar-radius-top) + 1px)
    calc(var(--bar-radius-bottom) + 1px) calc(var(--bar-radius-bottom) + 1px);

  background-color: var(--surface-glass);
  box-shadow: var(--shadow-md);
  opacity: 0;
  /* 표면이 켜지는 것은 수축과 함께(느리게), 모서리가 펴지는 것은 패널과 함께(빠르게).
     모서리를 여기 넣지 않았던 탓에 열 때 표면만 한 프레임에 각졌다. */
  transition:
    opacity       var(--dur-shrink) var(--ease-out),
    border-radius var(--dur-open)   var(--ease-out);
}

/* 인트로 위에 겹쳐 있을 때의 텍스트 대비.
   인트로 배경의 버건디 얼룩(합성 결과 약 #E7D5D3) 위에서
   기본 보조색 #6B645E 는 4.10:1 로 기준(4.5:1) 미달이다.
   한 단계 어두운 #55504B 로 바꾸면 5.62:1 이 되어 통과한다. */
.site-header .label {
  color: var(--color-text-sub-deep);
}

/*
 * 초기 렌더 동안만 헤더 전환을 끈다.
 *
 * 스크롤을 내린 상태에서 새로고침하면, 브라우저가 스크롤 위치를 되살린 뒤에야
 * ui.js 가 .is-shrunk 를 붙인다. 그 사이 헤더는 "최상단 = 전체 폭" 모습으로
 * 한 번 그려지고, 클래스가 붙는 순간 0.55초짜리 수축 전환이 재생된다.
 * → 눈에는 "펼쳐졌다가 캡슐로 접히는" 깜빡임으로 보인다.
 *
 * 상태를 먼저 정하는 것(ui.js)과 그 첫 적용을 애니메이션하지 않는 것(여기),
 * 둘 다 있어야 사라진다. 하나만으로는 위치가 늦게 잡히거나 늦게 잡힌 뒤 미끄러진다.
 *
 * is-preload 는 <head> 인라인 스크립트가 붙이고 ui.js 가 뗀다.
 * * 로는 가상요소(::before)가 잡히지 않으므로 유리 표면을 따로 적는다.
 */
:root.is-preload .site-header,
:root.is-preload .site-header *,
:root.is-preload .site-header__bar::before {
  transition: none !important;
}

/* ---- 수축 상태 : JS가 헤더에 .is-shrunk 를 붙인다 ----
   모바일에서 이 클래스가 하는 일은 아래 둘뿐이다 — 테두리 색과 표면 켜기.
   폭·모양을 바꾸는 토큰은 768px 이상에서만 달라진다(3-5 미디어쿼리).
   캡슐 반지름을 --radius-pill 대신 높이의 절반으로 직접 계산하는 이유도 거기 적었다. */

.site-header.is-shrunk .site-header__bar {
  border-color: var(--color-border);
}

/* 유리 표면을 켠다. 바뀌는 값은 opacity 하나뿐이고,
   블러는 이 시점에 이미 최종 반지름으로 켜져 있다(위 ::before 주석 참고).
   미지원 브라우저에서는 반투명 배경만 적용된다 — 점진적 향상. */
.site-header.is-shrunk .site-header__bar::before {
  opacity: 1;
  -webkit-backdrop-filter: var(--surface-blur);
  backdrop-filter: var(--surface-blur);
}

/* 배경이 생긴 뒤에는 종이색 위이므로 기본 보조색으로 되돌린다 */
.site-header.is-shrunk .label {
  color: var(--color-text-sub);
}

/* 드롭다운이 열리면 바와 패널이 한 덩어리가 되어야 하므로 아래쪽 모서리를 편다.
   토큰 하나만 바꾸면 바 · 유리 표면(::before)이 동시에 따라온다.
   예전에는 이 처리가 바에만 걸려 있어서, 표면을 ::before 로 옮긴 뒤
   테두리는 각졌는데 배경만 둥글게 남아 패널과 떠 보였다.
   .is-open 은 JS가 붙인다(:has() 를 쓰지 않는 이유는 구형 브라우저 대응). */
.site-header.is-open {
  --bar-radius-bottom: 0px;   /* 단위 필수 — 위 --bar-radius 주석 참고 */
}

.site-header.is-open .site-header__bar {
  border-color: var(--color-border);

  /* 패널과 맞닿는 아래 테두리는 그리지 않는다.
     테두리 자리는 배경이 그대로 채우므로(배경은 기본적으로 테두리 아래까지 깔린다)
     높이는 1px 도 변하지 않으면서 선만 사라져 두 요소가 이어져 보인다. */
  border-bottom-color: transparent;
}

/* 열린 동안에는 캡슐도 드롭다운과 같은 불투명도로 올라간다.
   값이 다르면 맞닿은 경계에서 표면 차이가 드러나 두 덩어리로 보인다.

   최상단에서 연 경우에는 아직 opacity 가 0 이므로 여기서 1 로 올려 준다.
   그래야 인트로 위에서도 메뉴 글자 뒤에 면이 깔려 읽힌다. */
.site-header.is-open .site-header__bar::before {
  opacity: 1;

  /*
   * 아래로 삐져나온 1px 을 거둔다.
   *
   * 평소에는 inset:-1px 로 테두리 자리까지 덮지만, 열린 상태에서는 바로 아래에
   * 드롭다운이 맞닿는다(실측: 바 아래끝 74.0px = 패널 위끝 74.0px).
   * 그대로 두면 표면이 패널 위로 1px 겹치는데, 둘 다 반투명이라 그 한 줄만
   * 배경이 두 번 칠해지고 블러도 두 겹 쌓여 가느다란 띠로 보인다.
   * (.nav-panel 의 margin 주석이 경계하는 것과 같은 현상이다)
   *
   * 이때 아래 모서리 반지름은 calc(0px + 1px) = 1px 로 남는다. 상자가 1px 만
   * 위로 줄었으니 엄밀히는 0 이어야 맞지만, 1px 을 없애려고 여기에 모서리
   * 재정의를 다시 넣지 않는다. 소비자마다 모서리를 따로 판단하던 것이
   * 이 문제를 세 번 만든 원인이고, 1px 은 화면에서 구분되지 않는다.
   */
  bottom: 0;

  background-color: var(--surface-glass-solid);
  -webkit-backdrop-filter: var(--surface-blur-strong);
  backdrop-filter: var(--surface-blur-strong);
}

/* ---- 3-1. 브랜드 ---- */
.brand {
  display: flex;
  align-items: baseline;

  /*
   * gap 을 쓰지 않고 부제 쪽 margin 으로 간격을 준다.
   * 수축할 때 부제와 함께 그 앞 간격까지 접혀야 하는데, gap 은 부모가 가진
   * 값이라 자식 하나만 골라 접을 수 없다. margin 이면 부제와 한 몸으로 접힌다.
   */
  color: var(--color-text);
  white-space: nowrap;
  text-decoration: none;

  /* 캡슐이 380px 까지 좁아지므로 사이트명이 길면 넘칠 수 있다.
     flex 자식의 기본값은 min-width:auto 라서 글자가 칸을 밀어낸다.
     0 으로 눌러야 아래 ellipsis 가 동작한다. */
  min-width: 0;
}

.brand:hover { color: var(--color-text); text-decoration: none; }

.brand__name {
  font-size: var(--fs-h4);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-heading);
  overflow: hidden;
  text-overflow: ellipsis;   /* 380px 캡슐에서 사이트명이 길 때 말줄임 */

  /* 본문 기본 행간(1.7)을 그대로 쓰면 18px 글자가 31px 높이를 차지해
     바가 min-height 를 넘어선다. 한 줄짜리 UI 텍스트는 행간을 조인다. */
  line-height: 1.2;
}

/*
 * 수축하면 부제("Web Publisher")는 자리를 내준다.
 *
 * 예전에는 opacity(0.28s 전환) + position:absolute(전환 없음) 조합이었다.
 * position 은 전환되지 않으므로 클래스가 붙는 즉시 부제가 흐름에서 빠지고,
 * 사이트명이 그 순간 왼쪽으로 툭 당겨진다. 그 위에 유령 글자만 0.28초 동안
 * 천천히 사라진다 — "글자는 아직 보이는데 배치는 이미 끝나 있는" 상태다.
 * 캡슐 수축이 0.55초 동안 계속되는 중이라 어긋남이 더 도드라졌다.
 *
 * 지금은 폭(max-width)과 간격(margin-left)을 0 으로 접으면서 같이 흐려진다.
 * 셋 다 같은 시간이라 부제가 접히는 속도와 캡슐이 좁아지는 속도가 일치한다.
 *
 * max-width 를 10rem 으로 잡은 이유: "Web Publisher"의 실제 폭(모노 11px,
 * 자간 0.16em 기준 약 120px)보다 조금 크게 잡아야 어떤 글꼴에서도 잘리지 않는다.
 * 실제 폭보다 큰 구간은 화면에 변화가 없는데, --ease-out 이 앞쪽이 빠른
 * 곡선이라 그 구간을 순식간에 지난다.
 */
.brand__role {
  margin-left: var(--sp-3);
  max-width: 10rem;
  overflow: hidden;

  /* .brand 의 white-space:nowrap 이 상속되지만 여기에도 직접 적는다.
     max-width 를 0 으로 접는 요소에서 이 값이 빠지면 글자가 한 글자씩
     줄바꿈되어 요소 높이가 몇 배로 늘고, 그 높이가 바의 min-height 를
     밀어내 캡슐이 뭉뚝해진다(.nav-toggle__text 주석 참고).
     상속에만 기대면 부모에서 한 줄 지웠을 때 원인을 찾기 어렵다. */
  white-space: nowrap;

  transition:
    max-width   var(--dur-shrink) var(--ease-out),
    margin-left var(--dur-shrink) var(--ease-out),
    opacity     var(--dur-shrink) var(--ease-out);
}

/* 접는 것은 768px 이상에서만 — 파일 끝 미디어쿼리 참고.
   모바일에서는 바가 좁아지지 않으므로 부제를 치울 이유가 없다. */

/* ---- 3-2. 가로 메뉴 (태블릿 이상에서만 표시) ---- */
.gnb { display: none; }

.gnb__list {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  list-style: none;
}

.gnb__link {
  position: relative;
  display: inline-block;
  padding-block: var(--sp-2);
  color: var(--color-text);
  font-size: var(--fs-sm);
  line-height: 1.2;   /* 바 높이를 62px 안에 들어오게 하는 핵심 값 */
}

/* 밑줄을 text-decoration 대신 ::after 로 그리는 이유:
   transform 으로 좌→우 확장 애니메이션을 줄 수 있고,
   transform 은 레이아웃을 건드리지 않아 성능이 좋다. */
.gnb__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background-color: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-base) var(--ease-out);
}

.gnb__link:hover {
  color: var(--color-accent);
  text-decoration: none;
}

.gnb__link:hover::after,
.gnb__link:focus-visible::after { transform: scaleX(1); }

/* ---- 3-3. 햄버거 버튼 ---- */
.nav-toggle {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-pill);
  color: var(--color-text);

  /* 좌우 패딩도 캡슐 수축과 같은 속도로 줄어야 버튼만 따로 튀지 않는다.
     (간격은 gap 이 아니라 .nav-toggle__text 의 margin 이 맡는다 —
      브랜드 부제와 같은 이유로, 글자와 간격이 한 몸으로 접혀야 한다) */
  transition: padding var(--dur-shrink) var(--ease-out);
}

/* 막대 2줄을 가상요소 없이 그라디언트 하나로 그린다 */
.nav-toggle__bars {
  width: 18px;
  height: 10px;
  border-top: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transition: transform var(--dur-base) var(--ease-out);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bars {
  transform: rotate(90deg);
}

/*
 * 캡슐로 수축하면 "Menu" 글자를 빼고 막대만 남긴다.
 *
 * 예전에는 .sr-only 와 같은 잘라내기(clip-path: inset(50%))를 붙였다.
 * clip-path 는 전환 대상이 아니어서 글자가 한 프레임에 사라졌고, 같은 순간
 * gap 과 padding 도 전환 없이 줄어 버튼이 툭 좁아졌다. 캡슐은 아직 0.55초
 * 동안 좁아지는 중이라 버튼만 먼저 도착해 보였다.
 *
 * 지금은 브랜드 부제와 같은 방식으로 접는다 — 폭과 간격을 0 으로 줄이면서
 * 흐려지고, 시간은 캡슐과 같다.
 *
 * 접근성:
 * display:none 이나 visibility:hidden 을 쓰지 않는 이유는 그대로다.
 * 그 둘은 접근성 트리에서 요소를 빼기 때문에 버튼의 이름이 사라지고,
 * 화면을 못 보는 사용자에게는 "버튼" 이라고만 읽히는 정체불명의 버튼이 된다.
 * max-width:0 + overflow:hidden 은 트리에 그대로 남아 이름이 "Menu" 로 유지된다.
 */
.nav-toggle__text {
  margin-left: var(--sp-2);
  max-width: 6rem;
  overflow: hidden;

  /*
   * 이 한 줄이 없으면 캡슐이 알약 모양을 잃는다.
   *
   * 수축할 때 max-width 를 0 으로 접는데, 줄바꿈을 막지 않으면 폭이 0 인 칸에
   * 글자를 넣으려고 브라우저가 "Menu" 를 한 글자씩 네 줄로 쪼갠다.
   * overflow:hidden 이 눈에는 안 보이게 잘라 줄 뿐, 요소의 높이는 네 줄
   * (11px * 1.4 * 4 = 61.6px)이 된다.
   *
   * 그 높이가 바의 min-height(62px)를 밀어내 실제 바가 103.56px 이 되는데
   * 반지름은 여전히 --bar-h 기준 31px 이라, 완전한 알약에 필요한 51.78px 에
   * 20px 넘게 모자란 뭉뚝한 모양이 된다. (헤드리스 크롬 실측값)
   *
   * 예전 .sr-only 방식에도 white-space: nowrap 이 들어 있었다.
   * max-width 로 접는 방식으로 바꾸면서 그 줄을 빠뜨린 것이 원인이었다.
   */
  white-space: nowrap;

  transition:
    max-width   var(--dur-shrink) var(--ease-out),
    margin-left var(--dur-shrink) var(--ease-out),
    opacity     var(--dur-shrink) var(--ease-out);
}

/* "Menu" 글자를 접는 것도 768px 이상에서만 — 파일 끝 미디어쿼리 참고.
   모바일에서는 바가 화면 폭 그대로라 글자를 뺄 이유가 없고,
   빼면 오히려 스크롤할 때마다 버튼 모양이 바뀌어 시선을 뺏는다. */

/* ---- 3-4. 드롭다운 패널 ----
   hidden 속성으로 닫아 둔다. display:none 이므로 Tab 순서에서도 완전히 빠진다.
   (visibility/opacity 로만 숨기면 보이지 않는 링크에 포커스가 갇힌다) */
.nav-panel[hidden] { display: none; }

.nav-panel {
  pointer-events: auto;

  /* 폭을 바와 같은 --bar-max 로 묶는다. 캡슐로 수축하면 패널도 같이 좁아져
     두 요소의 좌우 끝이 항상 정확히 맞는다. */
  max-width: var(--bar-max);

  /* 바와 1px 도 겹치지 않게 붙인다.
     겹치면 그 줄만 (1) 반투명 배경이 두 번 칠해지고 (2) backdrop-filter 가
     두 번 적용되어 블러가 두 겹 쌓인다 → 얇은 띠처럼 보인다.
     바의 아래 테두리를 transparent 로 지웠으므로 겹치지 않아도 이음매가 없다. */
  margin: 0 auto;

  padding: var(--sp-4) var(--gutter) var(--sp-5);

  /* 모바일에서는 바와 마찬가지로 아래쪽만 그린다. 화면 끝까지 펼쳐지므로
     좌우 선이 화면 가장자리에 걸린다. 768px 부터 네 변으로 되돌린다. */
  border: 0 solid var(--color-border);
  border-bottom-width: 1px;
  /* 위는 바와 맞닿으므로 펴고, 아래는 캡슐의 곡률을 그대로 이어받는다.
     여기만 --bar-radius-bottom 이 아니라 --bar-radius 를 읽는다.
     아래쪽 토큰은 "열렸을 때 0" 이라는 뜻인데 패널은 열렸을 때만 존재하므로,
     그 토큰을 쓰면 패널 밑동이 항상 각지게 된다. 이 줄은 바꾸지 말 것. */
  border-radius: 0 0 var(--bar-radius) var(--bar-radius);
  background-color: var(--surface-glass-solid);     /* 열린 캡슐과 같은 표면값 */
  box-shadow: var(--shadow-lg);
  -webkit-backdrop-filter: var(--surface-blur-strong);
  backdrop-filter: var(--surface-blur-strong);

  /* 화면보다 메뉴가 길어질 때를 대비. 항목이 위로 밀려 올라오는 진입 모션이
     패널 밖으로 새지 않도록 잘라내는 역할도 겸한다. */
  max-height: calc(100vh - var(--header-h) - var(--sp-6));
  overflow: auto;

  /*
   * 흘러내리는 진입 모션.
   *
   * height / max-height 를 애니메이션하는 흔한 방식은 매 프레임 레이아웃을
   * 다시 계산한다(요소의 크기가 바뀌므로 형제·자식 배치가 전부 재계산됨).
   * 반면 clip-path 는 "이미 배치가 끝난 픽셀 중 어디까지 보여줄지"만 정하므로
   * 레이아웃·페인트를 다시 하지 않고 합성(composite) 단계에서만 처리된다.
   * inset(0 0 100% 0) → inset(0 0 0 0) 이 곧 "위에서 아래로 걷히는" 모션이다.
   *
   * fill-mode 를 주지 않은 이유: 끝나면 기본 상태(clip-path 없음)로 돌아가야
   * 패널의 box-shadow 가 잘리지 않는다. (clip-path 는 그림자까지 잘라낸다)
   */
  animation: nav-panel-reveal var(--dur-open) var(--ease-out);
  transform-origin: top center;
}

@keyframes nav-panel-reveal {
  from { clip-path: inset(0 0 100% 0); opacity: 0.35; }
  to   { clip-path: inset(0 0 0 0);    opacity: 1; }
}

.nav-panel__list {
  display: flex;
  flex-direction: column;
  list-style: none;
}

/*
 * 항목 순차 낙하.
 * transform(translateY) 과 opacity 만 사용한다 — 둘 다 레이아웃/페인트를
 * 유발하지 않고 GPU 합성만으로 처리되는 속성이다.
 * margin-top 이나 height 로 같은 연출을 하면 항목마다 리플로우가 발생한다.
 *
 * fill-mode: backwards
 *   지연(delay) 이 끝나기 전까지 from 상태(투명)를 유지시켜 준다.
 *   이게 없으면 모든 항목이 일단 보였다가 하나씩 다시 사라졌다 나타난다.
 *   애니메이션이 끝난 뒤에는 기본 상태(보임)로 돌아가므로 강제 스타일이 남지 않는다.
 *
 * --i 는 head.php 에서 항목마다 인라인으로 넣는 순번이다.
 */
.nav-panel__item {
  animation: nav-item-drop var(--dur-base) var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 55ms + 70ms);
}

@keyframes nav-item-drop {
  from { opacity: 0; transform: translateY(-14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/*
 * 모션 최소화 설정 : 애니메이션을 아예 걸지 않고 즉시 최종 상태로 둔다.
 * base.css 의 전역 규칙이 duration 을 0.01ms 로 눌러 주긴 하지만,
 * animation-delay 까지는 건드리지 않아 항목이 최대 0.3초간 안 보일 수 있다.
 * 그래서 여기서 delay 를 포함해 명시적으로 끈다.
 */
@media (prefers-reduced-motion: reduce) {
  .nav-panel,
  .nav-panel__item {
    animation: none;
    opacity: 1;
    clip-path: none;
    transform: none;
  }
}

.nav-panel__item + .nav-panel__item {
  border-top: 1px solid var(--color-border);
}

.nav-panel__link {
  display: block;
  padding-block: var(--sp-4);
  color: var(--color-text);
  font-size: var(--fs-h3);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-heading);
}

.nav-panel__link:hover {
  color: var(--color-accent);
  text-decoration: none;
}

/* 데스크톱/태블릿 : 가로 메뉴를 쓰고, 수축했을 때만 햄버거로 전환 */
/* ==========================================================================
   3-5. 태블릿 이상 : 캡슐 전환을 켠다
   --------------------------------------------------------------------------
   왜 768px 인가

   이 값은 프로젝트가 정해 둔 세 구간(모바일 ~767 / 태블릿 768~1023 /
   데스크톱 1024~)의 첫 경계이고, 헤더에서 이미 의미를 갖는 지점이다.
   여기서 가로 메뉴(.gnb)가 나타나고 햄버거가 숨는다. 즉 "헤더의 구성이
   바뀌는 자리"가 원래 여기라, 캡슐 여부도 같은 선에 맞추면 경계가 하나로 모인다.
   새 구간을 만들지 않는다는 규칙에도 맞는다.

   내용상으로도 이 선이 맞다. 수축 목표 폭이 380px 인데,
     - 767px 이하 : 화면이 이미 380px 언저리라 폭이 거의 줄지 않는다.
                    모양만 바뀌고 얻는 것이 없다.
     - 768px 이상 : 768 -> 380 으로 절반 이하가 되므로 수축이 실제로 보인다.
                    가로 메뉴가 햄버거로 접히는 변화와도 맞물린다.
   ========================================================================== */

@media (min-width: 768px) {
  .gnb { display: block; }
  .nav-toggle { display: none; }

  .site-header.is-shrunk .gnb { display: none; }
  .site-header.is-shrunk .nav-toggle { display: inline-flex; }

  /* ---- 캡슐 기본 모양 ---- */
  .site-header {
    padding: var(--sp-3) var(--gutter);   /* 화면 가장자리에서 띄운다 */
    --bar-max: var(--container-wide);
    --bar-row-pad: var(--sp-5);           /* 24px — 시안 */
    --bar-radius: 1.25rem;                /* 20px — 시안 */
  }

  /* 떠 있는 상자이므로 네 변을 모두 그린다 (모바일은 아래쪽만) */
  .site-header__bar { border-width: 1px; }

  /* 드롭다운도 같이 되돌린다. 위쪽은 바와 맞닿으므로 계속 지운 채로 둔다. */
  .nav-panel {
    padding-inline: var(--sp-5);
    border-width: 0 1px 1px;
  }

  /* ---- 수축 상태 ----
     캡슐의 반지름에 --radius-pill(999px) 을 쓰지 않고 높이의 절반을 직접 계산한다.

     이유: CSS 는 "한 변에 접한 두 반지름의 합"이 변 길이를 넘으면
     네 모서리 전부에 같은 축소 비율(f)을 곱해 줄인다.
       - 닫힌 상태(네 모서리 999px): 좌변 999+999 > 62 → 실제 31px (완전한 알약)
       - 열린 상태(위 999px, 아래 0): 좌변 999+0 > 62 → 실제 62px
     같은 999px 를 줬는데도 아래를 0으로 펴는 순간 위쪽 반지름이 두 배가 된다.
     이게 "드롭다운을 열면 상단 모서리 모양이 바뀌던" 원인이었다.
     축소가 일어나지 않는 값을 쓰면 열든 닫든 위쪽 모서리가 그대로 유지된다. */
  .site-header.is-shrunk {
    --bar-max: 23.75rem;                  /* 380px — 시안 */
    --bar-row-pad: 0.875rem;              /* 14px  — 시안 */
    --bar-radius: calc(var(--bar-h) / 2);
  }

  /* ---- 폭이 줄어드는 만큼 안쪽 글자도 접는다 ----
     세 규칙 모두 --dur-shrink 로 캡슐과 같은 속도로 움직인다.
     (각 요소의 기본값과 접는 방식은 위 .brand__role / .nav-toggle__text 참고) */
  .site-header.is-shrunk .brand__role,
  .site-header.is-shrunk .nav-toggle__text {
    max-width: 0;
    margin-left: 0;
    opacity: 0;
  }

  .site-header.is-shrunk .nav-toggle {
    padding-inline: var(--sp-2);
  }
}


/* ==========================================================================
   4. 본문 · 페이지 머리말
   ========================================================================== */

.site-main {
  /* 헤더가 fixed 라 자리를 차지하지 않는다. 첫 요소가 스스로 여백을 확보하며,
     메인 자체에는 여백을 주지 않는다(인트로는 헤더 뒤까지 배경을 채워야 하므로). */
  min-height: 60vh;
}

/* 인트로가 없는 페이지 : 제목이 고정 헤더 밑에 깔리지 않도록 그 높이만큼 더 띄운다 */
.page-head {
  padding-block: calc(var(--header-h) + var(--sp-7)) var(--sp-7);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--sp-8);
}

.page-head__title {
  margin-top: var(--sp-3);
  font-size: var(--fs-h1);
  font-weight: var(--fw-regular);
}


/* ==========================================================================
   5. 푸터
   시안 기준으로 푸터는 종이 배경(기본 배색)이다.
   색 반전은 소개 섹션이 맡는다(main.css .about).
   ========================================================================== */

.site-footer {
  --footer-pad-b: 2.125rem;                      /* 34 — 시안 값. 아래 has-js 규칙이 여기에 더한다 */
  padding-block: 4.875rem var(--footer-pad-b);   /* 78 / 34 — 시안 값 */
  margin-top: var(--sp-section);
  overflow: hidden;   /* 워드마크가 가로로 넘칠 때 페이지 가로스크롤 방지 */
}

/*
 * 맨 위로 버튼이 있을 때만 아래 여백을 더 잡는다.
 *
 * 버튼은 화면 오른쪽 아래에 고정돼 있고, 페이지 끝까지 내리면 그 자리에 푸터의
 * 마지막 줄(저작권 · BUILT WITH…)이 온다. 그대로 두면 버튼이 그 글자를 덮는다.
 * 시안 값(34px)은 건드리지 않고, 버튼이 차지하는 높이만 아래에 더한다.
 *
 * has-js 로 조건을 건 이유: 스크립트가 없으면 버튼도 없으니, 그 화면에서는
 * 이유 없이 비어 보이는 여백이 된다.
 */
:root.has-js .site-footer {
  padding-bottom: calc(var(--footer-pad-b) + var(--to-top-clearance));
}

/* 연락처 · 워드마크 · 저작권 세 덩어리의 간격을 gap 하나로 관리한다 */
.site-footer__inner {
  display: flex;
  flex-direction: column;
  gap: 46px;
}

.site-footer__top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-5) var(--sp-7);
}

.site-footer__contact {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.site-footer__email {
  font-size: clamp(1.25rem, 1.1rem + 0.62vw, 1.625rem);   /* 20 → 26 */
  letter-spacing: var(--ls-heading);
  color: var(--color-text);
}

.site-footer__email:hover { color: var(--color-accent); }

.site-footer__nav {
  display: flex;
  flex-wrap: wrap;
  gap: 30px;
  font-family: var(--font-mono);
  font-size: var(--fs-label-sm);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.site-footer__nav a { color: var(--color-text-sub); }
.site-footer__nav a:hover { color: var(--color-accent); text-decoration: none; }

/* 대형 워드마크 — 시안은 잉크색 불투명이다.
   종이 배경으로 바뀌면서 흐리게 처리할 이유가 없어졌으므로 opacity 를 뺐다. */
.site-footer__wordmark {
  font-size: var(--fs-wordmark);
  font-weight: var(--fw-medium);   /* 시안 500 */
  line-height: 0.82;
  letter-spacing: -0.05em;
  color: var(--c-ink);
  white-space: nowrap;
  user-select: none;
}

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3) var(--sp-4);
  padding-top: 18px;
  border-top: 1px solid var(--color-border);
  font-family: var(--font-mono);
  font-size: var(--fs-label-sm);
  letter-spacing: 0.04em;
  color: var(--color-text-sub);
}

/* ==========================================================================
   5-1. 맨 위로
   --------------------------------------------------------------------------
   시안에 없는 요소다. 그래서 모양은 사이트 규칙에서만 끌어왔다.
     - 각진 모서리(반지름 0)
     - 강조색을 쓰지 않는다. 모든 페이지에 상시 떠 있는 요소라 여기에 버건디를
       쓰면 "강조색은 화면당 한 곳" 규칙이 매 화면에서 깨진다.
       평소엔 종이 배경 + 잉크 테두리, 호버에서 색을 뒤집는 것으로만 반응한다.
   ========================================================================== */

.to-top {
  position: fixed;
  /* 화면 가장자리에서 띄우는 거리. 본문 좌우 여백과 같은 토큰을 써서 화면 폭에
     따라 함께 움직인다(좁으면 20px, 넓으면 64px).
     컨테이너에는 최대 폭이 걸려 있으므로 넓은 화면에서 본문 오른쪽 끝과 딱 맞지는
     않는다 — 화면에 고정된 요소라 기준을 뷰포트로 잡는 쪽이 맞다. */
  right: var(--gutter);
  bottom: var(--gutter);
  z-index: var(--z-sticky);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--to-top-size);
  height: var(--to-top-size);

  border: 1px solid var(--c-ink);
  border-radius: 0;
  background-color: var(--color-bg);
  color: var(--c-ink);

  /*
   * 기본은 "없는 것"과 같은 상태다.
   * visibility 까지 끄는 이유는 Tab 순서 때문이다. opacity 만 0 으로 두면
   * 화면에 안 보이는 버튼에 포커스가 잡혀, 키보드 사용자는 아무것도 없는 곳에서
   * 포커스를 잃은 것처럼 느낀다.
   */
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);

  /*
   * visibility 는 중간값이 없어 전환이 안 먹는다(hidden→visible 이 즉시 튄다).
   * 그래서 "지연 시간만" 준다. 사라질 때는 페이드가 끝난 뒤에 hidden 이 되고,
   * 나타날 때는 아래 .is-visible 이 지연을 0 으로 덮어 즉시 visible 이 된다.
   */
  transition:
    opacity          var(--dur-base) var(--ease-out),
    transform        var(--dur-base) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out),
    color            var(--dur-fast) var(--ease-out),
    visibility       0s linear var(--dur-base);
}

/* ui.js 가 붙인다. 첫 화면을 지나 내려갔을 때만. */
.to-top.is-visible {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}

.to-top:hover {
  background-color: var(--c-ink);
  color: var(--color-text-invert);
}

.to-top__icon {
  width: 1rem;
  height: 1rem;
  /* 선 끝을 둥글리지 않는다(stroke-linecap 기본값 butt). 각진 톤을 화살표에서도 유지. */
}


/* ==========================================================================
   6. 공통 컴포넌트 (여러 페이지에서 쓰는 최소 단위만)
   ========================================================================== */

/* 태그 칩 — 서브 컬러(딥 그린) 사용 구역.
   같은 화면에서 버건디 강조와 나란히 두지 않는다.
   각진 모서리 · 딥 그린 테두리 · 자간 0.1em 은 모두 시안 값이다. */
.chip {
  display: inline-block;
  padding: 6px 11px;
  border: 1px solid var(--color-accent-alt);
  font-family: var(--font-mono);
  font-size: var(--fs-label-xs); /* 10px — 시안 */
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-accent-alt);
  white-space: nowrap;
}

/* 텍스트 링크 버튼 */
.btn-text {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding-block: var(--sp-2);
  border-bottom: 1px solid currentColor;
  color: var(--color-accent);
  font-size: var(--fs-sm);
}

.btn-text:hover { color: var(--color-accent-hover); text-decoration: none; }
