/* 빨간딱지 — 잉크 테두리, radius 0, 하드 오프셋 그림자, 빨강과 형광 노랑.
   규칙 한 줄: 튀어나온 건 누르는 것, 눌린 건 켜진 것. 카드는 둘 다 아니라서 평평하다. */
:root {
  --ink: #111111; --paper: #fffdf7; --cream: #f1eadb;
  --muted: rgba(17, 17, 17, .55); --dot: rgba(17, 17, 17, .08);
  /* 빨강이 둘인 이유: 채움색과 글자색은 상대가 다르다. 채움 위에는 종이색 글자가
     얹히고(#fffdf7 대비 4.9:1), 글자일 때는 배경이 종이나 먹이다. 하나로 쓰면
     다크에서 '이력 3건' 같은 작은 빨간 글자가 3.5:1로 떨어진다 */
  --red: #d42a22; --red-text: var(--red);
  --yellow: #ffe500; --blue: #2f5bea;
  --green: #0f7a4a; --pink: #ff6fae;
  /* 본문은 기기에 있는 한글로 그린다. 웹폰트로 받으면 한 화면에 262KB가 붙는데,
     그 값을 내고 얻는 인상 차이가 가장 작은 자리가 여기다 — 제호·가격·탭이 인상을 진다.
     미루기(font-display:optional)로는 이 무게가 줄지 않는다: 브라우저는 어차피 끝까지
     받아놓고 그 페이지에서 안 쓸 뿐이고, 슬라이스마다 따로 판단해서 무한스크롤로 뒤늦게
     도착한 제목만 다른 서체로 그려진다 */
  --sans: system-ui, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
  --disp: "Black Han Sans", var(--sans);

  /* 옛 토큰은 지우지 않고 새 토큰으로 잇는다 — 아직 다시 조판하지 않은 관리자·알림 관리
     화면 수십 줄이 이 별칭만으로 새 팔레트를 따라온다 */
  --bg: var(--cream); --card: var(--paper); --text: var(--ink);
  --line: var(--ink); --accent: var(--red);
  --chip: var(--cream); --chip-active: var(--ink);
}
/* 다크는 잉크와 종이만 뒤집는다. 토글이 양방향으로 이기려면 세 곳에 다 있어야 한다 —
   시스템 설정, 명시적 라이트 선택, 명시적 다크 선택 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink: #f5f1e8; --paper: #16150f; --cream: #0e0d0a;
    --muted: rgba(245, 241, 232, .6); --dot: rgba(245, 241, 232, .06);
    --green: #22b573; --red-text: #ff8a7d;
  }
}
:root[data-theme="dark"] {
  --ink: #f5f1e8; --paper: #16150f; --cream: #0e0d0a;
  --muted: rgba(245, 241, 232, .6); --dot: rgba(245, 241, 232, .06);
  --green: #22b573; --red-text: #ff8a7d;
}

* { box-sizing: border-box; }
/* display를 지정한 클래스가 hidden 속성을 덮어쓰는 것을 막는다 —
   .copy-fallback이 실제로 그렇게 항상 보였다 */
[hidden] { display: none !important; }
body {
  margin: 0; color: var(--text);
  background-color: var(--bg);
  /* 전단지 종이의 망점. 고정 오버레이로 얹으면 sticky·fixed와 쌓임 순서로 싸운다 */
  background-image: radial-gradient(var(--dot) .6px, transparent .6px);
  background-size: 6px 6px;
  font-family: var(--sans);
  font-size: 15px; line-height: 1.4;
}
a { color: inherit; text-decoration: none; }

/* ---------- 버튼 한 벌 ----------
   클래스 이름은 그대로 두고 규칙만 합류시킨다. 이름을 바꾸면 위임 클릭·상태 API·테스트가
   함께 깨진다. 카드 액션 줄의 네 버튼은 자기들끼리 한 규칙을 쓰므로 여기 넣지 않는다 */
.btn,
.alert-btn, .danger-btn,
.kw-go, .kw-alert, .kw-manual, .kw-close,
.sheet-actions .primary, .sheet-actions .ghost,
.dd-form button, .inline-form button, .kw-edit button,
.notice .primary-link, .float-btn, .icon-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 36px; padding: 0 14px;
  border: 2px solid var(--ink); border-radius: 0;
  background: var(--paper); color: var(--ink);
  font-family: inherit; font-size: 13px; font-weight: 700; line-height: 1;
  box-shadow: 3px 3px 0 var(--ink);
  cursor: pointer; white-space: nowrap; text-decoration: none;
  transition: transform 80ms, box-shadow 80ms;
}
.btn:hover,
.alert-btn:hover, .danger-btn:hover,
.kw-go:hover, .kw-alert:hover, .kw-manual:hover, .kw-close:hover,
.sheet-actions .primary:hover, .sheet-actions .ghost:hover,
.dd-form button:hover, .inline-form button:hover, .kw-edit button:hover,
.notice .primary-link:hover, .float-btn:hover, .icon-btn:hover {
  /* 색은 건드리지 않는다. 기본 규칙이 이미 종이/잉크를 주므로 평범한 버튼에는
     아무 차이가 없고, 여기서 칠하면 .primary의 빨강과 .kw-go의 먹이 지워진다 —
     가리키는 순간 강조가 사라지는 셈이다 */
  transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--ink);
}
.btn:active,
.alert-btn:active, .danger-btn:active,
.kw-go:active, .kw-alert:active, .kw-manual:active, .kw-close:active,
.sheet-actions .primary:active, .sheet-actions .ghost:active,
.dd-form button:active, .inline-form button:active, .kw-edit button:active,
.notice .primary-link:active, .float-btn:active, .icon-btn:active {
  transform: translate(2px, 2px); box-shadow: 0 0 0 var(--ink);
}
.btn:disabled,
.kw-go:disabled, .kw-alert:disabled {
  background: var(--cream); color: var(--muted); border-color: var(--muted);
  box-shadow: none; transform: none; cursor: default;
}
.btn:focus-visible, .chip:focus-visible, .dd > summary:focus-visible,
.card .react:focus-visible,
.card .kw-btn:focus-visible, .card .share:focus-visible {
  outline: 3px solid var(--blue); outline-offset: 2px;
}
/* 주된 행동은 빨강, 켜진 상태는 노랑 */
.btn.primary, .sheet-actions .primary, .kw-alert, .notice .primary-link, .dd-form button {
  background: var(--red); color: #fffdf7;
}
.btn.accent { background: var(--yellow); color: #111; }

/* ---------- 머리띠 ----------
   노랑 위는 밤에도 노랑이다 — 잉크색을 토큰이 아니라 값으로 고정한다 */
/* 머리띠 자체는 화면을 가로지르되 그 안의 내용은 본문과 같은 세로선에 선다.
   퍼센트라 스크롤바를 뺀 실제 폭을 쓴다 — 100vw로 하면 스크롤바 폭만큼 어긋난다 */
.topbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 8px max(12px, calc((100% - 1080px) / 2 + 12px));
  background: var(--yellow); color: #111; border-bottom: 3px solid #111;
  position: sticky; top: 0; z-index: 10;
}
/* 워드마크. 이모지를 떼고 디스플레이체 하나로 세운다 — 전단지 제호에 그림문자는 없다 */
.logo {
  order: 1; font-family: var(--disp); font-size: 44px; color: #111;
  letter-spacing: -.03em; white-space: nowrap; flex-shrink: 0;
}
.search { order: 2; flex: 1; min-width: 0; max-width: 420px; display: flex; }
/* 들어간 것은 입력하는 것 — 입력창만 안쪽 그림자를 쓴다 */
.search input {
  flex: 1; min-width: 0; min-height: 48px; padding: 8px 12px;
  border: 2px solid #111; border-radius: 0;
  background: #fffdf7; color: #111; font-family: inherit; font-size: 15px;
  box-shadow: inset 3px 3px 0 #111;
}
/* 튀어나온 건 누르는 것 — 제출은 빨강이다. 테두리는 입력창과 공유한다 */
.search-go {
  flex: none; width: 48px; border: 2px solid #111; border-left: 0; border-radius: 0;
  background: var(--red); color: #fffdf7; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.search-go:active { background: #111; }
/* 머리띠 위의 버튼은 토큰이 아니라 값으로 그린다 — 노랑은 밤에도 노랑이라
   var(--ink)를 쓰면 다크에서 크림색 테두리가 노랑 위에 뜬다 */
.topbar .icon-btn {
  flex: none; width: 40px; min-height: 40px; padding: 0;
  border-color: #111; background: #fffdf7; color: #111; box-shadow: 3px 3px 0 #111;
}
.topbar .icon-btn:hover { background: var(--yellow); color: #111; box-shadow: 4px 4px 0 #111; }
.topbar .icon-btn:active { box-shadow: 0 0 0 #111; }
/* 오른쪽 무리는 화면 끝에 붙는다. auto 여백은 그중 첫 번째가 가지는데,
   좁은 화면에서는 돋보기이고 넓은 화면에서는 돋보기가 없으니 종이 이어받는다 */
#search-btn { order: 3; margin-left: auto; }
.rss-link { order: 4; }
#theme-btn { order: 5; }
/* 넓은 화면에는 입력창이 이미 있다 */
@media (min-width: 768px) {
  #search-btn { display: none; }
  .rss-link { margin-left: auto; }
}
/* 좁은 화면: 입력창은 아랫줄로 내려가 접혀 있고, 돋보기가 편다 */
@media (max-width: 767px) {
  .topbar { gap: 8px; padding: 8px 12px; }
  .logo { font-size: 26px; }
  .search { order: 9; flex-basis: 100%; max-width: none; }
  /* 좁은 화면에서는 제 줄을 통째로 쓰므로 48이면 머리띠가 두 칸처럼 두꺼워진다 */
  .search input, .search-go { min-height: 44px; }
  .search-go { width: 44px; }
  .topbar:not(.searching) .search { display: none; }
}
/* 지금 무엇을 누르면 되는지를 보여준다 — 라이트에서는 달, 다크에서는 해 */
.i-sun { display: none; }
:root[data-theme="dark"] .i-moon { display: none; }
:root[data-theme="dark"] .i-sun { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .i-moon { display: none; }
  :root:not([data-theme="light"]) .i-sun { display: block; }
}
/* 시안과 같은 1080px. 딜 행이 썸네일·내용·가격·버튼 네 칸으로 벌어지려면 760으로는
   좁다 — 제목이 두 줄로 접히고 가격 딱지가 붙을 자리가 없다 */
main { max-width: 1080px; margin: 0 auto; padding: 12px 12px 60px; }

/* ---------- 칩 ----------
   키워드 패널이 이 클래스를 그대로 재사용한다 (keyword-panel.js). 여기서 모양이 정해지면
   패널 칩도 함께 바뀐다 — 같은 제스처를 같은 모양으로 */
.filters { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  display: inline-flex; align-items: center; min-height: 32px; padding: 0 12px;
  border: 2px solid var(--ink); border-radius: 0;
  background: var(--paper); color: var(--ink);
  font-family: inherit; font-size: 13px; font-weight: 700; line-height: 1;
  box-shadow: 2px 2px 0 var(--ink); cursor: pointer; white-space: nowrap;
  transition: transform 80ms, box-shadow 80ms;
}
.chip:hover { transform: translate(-1px, -1px); box-shadow: 3px 3px 0 var(--ink); }
.chips.small .chip { min-height: 40px; font-size: 14px; padding: 0 14px; }
/* 넓은 화면에서 정렬은 줄 오른쪽 끝이다 — 왼쪽은 무엇으로 거를지, 오른쪽은 어떻게 줄 세울지.
   좁은 화면에서는 줄이 가로로 흐르므로 auto 여백이 칩들을 화면 밖으로 민다 */
@media (min-width: 768px) {
  .chips.small .chip.sort { order: 1; }
  .chips.small .chip.win { order: 2; }
  .chips.small .chip.sort:first-child { margin-left: auto; }
}
/* 눌린 건 켜진 것 — 배경만 바꾸지 않고 실제로 눌러 넣는다 */
.chip.active, .kw-chips .chip[aria-pressed="true"] {
  background: var(--tint, var(--ink)); color: var(--tint-fg, var(--paper));
  transform: translate(2px, 2px); box-shadow: 0 0 0 var(--ink);
}
.chip.active:hover, .kw-chips .chip[aria-pressed="true"]:hover {
  transform: translate(2px, 2px); box-shadow: 0 0 0 var(--ink);
}
/* 카테고리마다 자기 색. 서버는 색을 모르고 코드만 내려보낸다 */
[data-cat="elec"] { --tint: var(--blue); --tint-fg: #fffdf7; }
[data-cat="food"] { --tint: var(--green); --tint-fg: #fffdf7; }
[data-cat="beauty"] { --tint: var(--pink); --tint-fg: #111; }
[data-cat="event"] { --tint: var(--yellow); --tint-fg: #111; }
[data-cat="game"] { --tint: var(--ink); --tint-fg: var(--paper); }
[data-cat="etc"] { --tint: var(--cream); --tint-fg: var(--ink); }
/* 정렬과 그 기간 창은 다른 무리다 — 붙여두면 여섯 칩이 한 줄로 읽힌다 */
.chip.sort + .chip.win { margin-left: 8px; }
/* 모바일에서는 칩을 접지 않고 한 줄로 흘린다. 접으면 정렬 칩이 늘어난 뒤 필터가
   화면의 3할을 먹고 첫 딜이 접힘 아래로 밀린다 (375px에서 180px 실측).

   데스크탑은 그대로 접는다 — 거기서는 .dd-menu가 absolute라 overflow 컨테이너에
   잘려 필터가 통째로 안 보이게 된다. 모바일은 이미 fixed 바텀시트라 안 잘린다.
   .kw-chips는 카드 안 패널이라 여기 걸리지 않는다 — 계속 접힌다 */
@media (max-width: 767px) {
  .filters .chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    /* 스냅은 칩의 시작을 스크롤 시작에 붙인다 — 이게 없으면 첫 칩이 아래 padding을
       먹어치우고 화면 왼쪽 끝에 딱 붙는다 (실측: 로드 직후 scrollLeft가 14로 튐) */
    scroll-padding-left: 12px; scroll-padding-right: 12px;
    /* main의 좌우 여백만큼 빼서 칩이 화면 끝까지 흐르게 한다 — 끝에서 잘린 칩이
       '더 있다'는 유일한 신호다 */
    margin-left: -12px; margin-right: -12px;
    padding-left: 12px; padding-right: 12px;
    /* 칩의 하드 그림자 2px과 눌림 2px이 세로로 잘리지 않을 만큼.
       한 축이 auto가 되면 다른 축의 visible도 auto가 되기 때문이다 */
    padding-bottom: 4px;
  }
  .filters .chips::-webkit-scrollbar { display: none; }
  .filters .chips > * { flex: none; scroll-snap-align: start; }
}
/* ---------- 카테고리는 칩이 아니라 색인 탭이다 ----------
   아래 잉크 괘선 위에 얹혀 서고, 켜진 탭만 그 선을 뚫고 올라와 종이와 이어진다.
   두 번째 줄(.chips.small)은 계속 칩이다 — 정렬과 필터는 서류철 탭이 아니다 */
.filters .chips:not(.small) {
  align-items: flex-end; gap: 4px;
  border-bottom: 3px solid var(--ink); padding-bottom: 0;
}
.filters .chips:not(.small) .chip {
  min-height: 40px; padding: 0 18px;
  border-bottom: 0; background: var(--cream); box-shadow: none;
  font-family: var(--disp); font-weight: 400; font-size: 18px; letter-spacing: -.01em;
  transition: min-height 80ms;
}
.filters .chips:not(.small) .chip:hover {
  min-height: 42px; transform: none; box-shadow: none;
}
/* 켜진 탭은 눌리는 게 아니라 솟는다 — 여기서만 '눌린 건 켜진 것' 규칙을 쓰지 않는다 */
.filters .chips:not(.small) .chip.active,
.filters .chips:not(.small) .chip.active:hover {
  min-height: 44px;
  background: var(--tint, var(--paper)); color: var(--tint-fg, var(--ink));
  /* 켜진 탭만 오른쪽으로 그늘을 던진다 — 서류철에서 앞으로 당겨진 탭이 그렇다 */
  transform: none; box-shadow: 4px 0 0 var(--ink);
}
/* 좁은 화면의 탭은 한 마디 낮다 — 이 줄 뒤에 오는 이유는 미디어 쿼리가 특정도를
   보태지 않기 때문이다. 위에 두면 바로 아래 평범한 규칙에 진다 */
@media (max-width: 767px) {
  .filters .chips:not(.small) .chip { min-height: 32px; padding: 0 14px; font-size: 16px; }
  .filters .chips:not(.small) .chip:hover { min-height: 34px; }
  .filters .chips:not(.small) .chip.active,
  .filters .chips:not(.small) .chip.active:hover { min-height: 36px; }
}
.search-info { color: var(--muted); font-size: 13px; margin: 4px 0 10px; }

/* 드롭다운 필터 (알구몬식: 카테고리만 상시, 나머지는 메뉴) */
.dd { position: relative; }
.dd > summary { list-style: none; cursor: pointer; user-select: none; display: inline-flex; }
.dd > summary::-webkit-details-marker { display: none; }
.dd-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
  min-width: 150px; background: var(--paper); border: 2px solid var(--ink);
  border-radius: 0; padding: 6px; box-shadow: 4px 4px 0 var(--ink);
  display: flex; flex-direction: column;
}
.dd-item {
  padding: 8px 10px; border-radius: 0; font-size: 13px; white-space: nowrap;
  min-height: 36px; display: flex; align-items: center;
}
.dd-item:hover { background: var(--cream); }
.dd-item.on { font-weight: 700; }
.dd-item.on::before { content: "✓ "; color: var(--red-text); }
.dd-item.check::before { content: "☐ "; color: var(--muted); }
.dd-item.check.on::before { content: "☑ "; color: var(--red-text); }
.dd-form { display: flex; gap: 6px; align-items: center; padding: 4px; }
.dd-form input[type="number"] {
  width: 84px; padding: 7px 8px; border: 2px solid var(--ink); border-radius: 0;
  background: var(--paper); color: var(--text); font-family: inherit; font-size: 13px;
  box-shadow: inset 2px 2px 0 rgb(17 17 17 / .12);
}

/* ---------- 모바일 필터: 바텀시트 ----------
   마크업은 <details> 그대로다. data-pop 컨테이너로 갈아치우면 세 가지를 잃는다 —
   JS가 죽은 브라우저에서 필터를 열 수단, app.js의 상호배타 닫기 리스너 두 개,
   그리고 alerts-page.js가 사이트 선택기를 이 마크업으로 직접 만든다는 사실.
   그래서 여는 방식은 그대로 두고 '열린 메뉴의 모양'만 시트로 바꾼다.

   알림 관리의 선택기(.kw-edit .dd)는 시트로 만들지 않는다. 줄 안에 박힌 작은 컨트롤이라
   화면 전체를 덮을 일이 아니다 — 팝오버 모양은 위에서 이미 공유한다 */
@media (max-width: 767px) {
  .filters .dd[open] > .dd-menu {
    position: fixed; top: auto; left: 0; right: 0; bottom: 0;
    z-index: 30; min-width: 0; max-height: 85vh; overflow-y: auto;
    border: 0; border-top: 3px solid var(--ink); box-shadow: none;
    padding: 8px 12px calc(12px + env(safe-area-inset-bottom));
    animation: sheet-up 180ms ease-out;
  }
  /* 손잡이 — 팝오버가 아니라 시트라는 것을 눈에 알린다 */
  .filters .dd[open] > .dd-menu::before {
    content: ""; flex: none; align-self: center;
    width: 44px; height: 4px; margin: 4px 0 10px; background: var(--ink);
  }
  .filters .dd .dd-item { min-height: 48px; font-size: 15px; }
  .filters .dd .dd-form { flex-wrap: wrap; padding: 8px 4px; }
  .filters .dd .dd-form input[type="number"] { flex: 1; width: auto; min-height: 44px; }
  .filters .dd .dd-form button { min-height: 44px; }

  /* 스크림은 .filters가 갖는다. .dd의 가상요소로 두면 클릭 대상이 .dd 자신이라
     app.js의 `!dd.contains(e.target)`가 거짓이 되어 시트를 눌러도 닫히지 않는다 */
  .filters:has(.dd[open])::before {
    content: ""; position: fixed; inset: 0; z-index: 25; background: rgb(17 17 17 / .5);
  }
  /* :has()가 없으면 배경이 함께 스크롤될 뿐이다 — 시트는 그대로 뜬다 */
  body:has(.filters .dd[open]) { overflow: hidden; }
}
@keyframes sheet-up { from { transform: translateY(100%); } }
@media (prefers-reduced-motion: reduce) {
  .filters .dd[open] > .dd-menu { animation: none; }
}

/* ---------- 딜 카드 ----------
   카드는 누르는 것도 입력하는 것도 아니다 — 그림자를 주지 않는다. 게다가 .kw-box와
   .history-box가 카드 바로 아래에 이어 붙으므로, 그림자가 있으면 그 이음매에 그늘이 낀다 */
.card {
  --thumb: 88px;
  /* column-gap이다. 격자가 되는 폭에서 gap을 쓰면 행 사이에도 12px씩 끼어
     카드가 36px 길어진다 (실측) */
  display: flex; column-gap: 12px; padding: 12px; margin-bottom: 0;
  background: none; border: 0; border-left: 6px solid var(--ink);
  border-radius: 0;
  /* 인기 스티커와 종료 도장이 썸네일 위에 앉을 자리를 만든다. 둘은 .badges 안에
     있으므로(view-state.js가 그 줄에 '방금 봄'을 끼운다) 카드가 기준이 된다 */
  position: relative;
}
/* 아래는 절취선이다 — 전단지에서 오려 쓰는 쿠폰의 문법.
   카드에 이력·키워드 패널이 이어 붙으면 그 사이는 자를 자리가 아니라 한 덩어리이므로
   실선으로 되돌리고 가위도 거둔다 */
.card { border-bottom: 2px dashed var(--ink); }
/* 마지막 행 아래는 시트의 테두리가 이미 선을 긋는다 */
#deals .card:last-of-type { border-bottom: 0; }
.card::after {
  content: "✂"; position: absolute; left: 8px; bottom: 0;
  transform: translateY(55%); font-size: 12px; line-height: 1;
  opacity: .55; z-index: 1; pointer-events: none;
}
.card:has(+ .kw-box), .card:has(+ .history-box) { border-bottom-style: solid; }
.card:has(+ .kw-box)::after, .card:has(+ .history-box)::after { display: none; }
.card.ended { opacity: .55; }
.thumb { flex-shrink: 0; }
.thumb img, .noimg {
  width: var(--thumb); height: var(--thumb); border-radius: 0; object-fit: cover;
  border: 2px solid var(--ink);
  background: var(--chip); display: flex; align-items: center; justify-content: center;
  font-family: var(--disp); font-size: 22px; color: var(--ink);
}
.noimg { width: var(--thumb); height: var(--thumb); }
/* 누르면 사진이 종이에서 한 겹 들린다. 카드 자체는 그림자를 갖지 않으므로
   (아래 .kw-box가 이어 붙는다) 들리는 것은 사진 한 장뿐이다.
   손가락만 있는 기기에는 hover가 없어 :active가 이 효과의 유일한 입구다 */
.thumb img, .noimg { transition: transform 80ms, box-shadow 80ms; }
.thumb:active img, .thumb:active .noimg {
  transform: translate(-2px, -2px); box-shadow: 3px 3px 0 var(--ink);
}
/* 스티커와 도장은 사진 위에 붙어 있다 — 사진만 들리면 제자리에 남아 어긋난다 */
.thumb:active ~ .body .badge.pop { transform: translate(-2px, -2px) rotate(-12deg); }
.thumb:active ~ .body .badge.end {
  transform: translate(calc(-50% - 2px), calc(-50% - 2px)) rotate(-12deg);
}
@media (hover: hover) {
  .card:hover .thumb img, .card:hover .noimg {
    transform: translate(-2px, -2px); box-shadow: 3px 3px 0 var(--ink);
  }
  .card:hover .badge.pop { transform: translate(-2px, -2px) rotate(-12deg); }
  .card:hover .badge.end {
    transform: translate(calc(-50% - 2px), calc(-50% - 2px)) rotate(-12deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .thumb img, .noimg { transition: none; }
}
.body { min-width: 0; flex: 1; }
.badges { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin-bottom: 4px; }
.badge {
  font-size: 11px; font-weight: 700; line-height: 1.2; padding: 1px 6px; border-radius: 0;
  border: 2px solid var(--ink); background: var(--paper); color: var(--ink);
}
/* 출처는 손으로 찍은 도장이라 살짝 삐딱하다. 상점은 반대로 먹으로 눌러 찍은 블록 */
.badge.src {
  background: transparent; font-weight: 400; padding: 1px 6px;
  transform: rotate(-2deg);
}
.badge.shop { border: 0; padding: 3px 6px; background: var(--ink); color: var(--paper); font-size: 12px; }
.badge.cat { border-color: var(--muted); color: var(--muted); font-weight: 400; }
.badge.dup { border-style: dashed; background: transparent; color: var(--muted); }
/* 인기는 썸네일 모서리에 붙인 원형 스티커, 종료는 그 한가운데 찍은 고무도장 */
.badge.pop {
  position: absolute; top: 6px; left: 6px; z-index: 2;
  width: 34px; height: 34px; padding: 0; border-radius: 50%;
  background: var(--red); color: #fffdf7; transform: rotate(-12deg);
  display: inline-flex; align-items: center; justify-content: center; font-size: 11px;
}
/* 도장은 카드 안에서만 찍는다. --thumb이 .card에만 있어서, 이력 상자(.history-box는
   카드의 형제다)에서는 left/top이 통째로 무효가 되고 배지가 제자리를 잃는다 —
   흐름에서 빠진 채 윗줄 위로 끌려 올라가 좁은 화면에서는 화면 밖으로 나간다.
   밖에서는 위의 평범한 .badge 규칙만 받아 줄 끝에 얌전히 선다 */
.card .badge.end {
  position: absolute; z-index: 2;
  left: calc(12px + var(--thumb) / 2); top: calc(12px + var(--thumb) / 2);
  transform: translate(-50%, -50%) rotate(-12deg);
  background: transparent; color: var(--red); border: 3px solid var(--red);
  font-family: var(--disp); font-size: 19px; font-weight: 400;
  padding: 1px 8px; letter-spacing: .06em;
}
@media (max-width: 767px) { .card { --thumb: 72px; } }
.hist-link { color: var(--red-text); font-weight: 700; }
/* 왼쪽 띠는 카드에서 이어받아 6px을 유지한다 — 2px로 꺾이면 그 자리에 계단이 생긴다 */
.history-box {
  background: var(--card); border: 2px solid var(--ink); border-radius: 0;
  border-left-width: 6px;
  padding: 10px 14px; margin: 0; font-size: 13px;
}
/* 위가 열리고 위로 당겨지는 것은 카드에 이어 붙었을 때뿐이다. 딜 페이지에서는 제목
   아래에 혼자 서므로 닫혀 있어야 한다 — 안 그러면 위가 트인 채 제목을 8px 파고든다 */
.card + .history-box { border-top: 0; margin-top: 0; }
.history-box ul { list-style: none; margin: 0; padding: 0; }
.history-box li {
  padding: 4px 0; display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap;
  border-bottom: 1px dashed var(--muted);
}
.history-box li:last-child { border-bottom: 0; }
.history-box .lowest, .hist-summary .lowest {
  color: var(--red-text); font-weight: 800; margin: 0 0 4px;
}
.hist-more { display: inline-block; margin-top: 6px; color: var(--muted); font-size: 12px; }
.hist-summary {
  background: var(--card); border: 2px solid var(--ink); border-radius: 0;
  padding: 10px 14px; font-size: 14px; margin: 0 0 8px;
}
.card h3 {
  margin: 0 0 4px; font-size: 17px; line-height: 1.35; font-weight: 700;
  letter-spacing: -.01em; word-break: keep-all; overflow-wrap: break-word;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* 전단지에서 가격은 가장 큰 글자이고, 형광펜이 그어져 있다.
   자국은 .amount(인라인)가 진다 — 블록에 칠하면 가격 없는 딜에 빈 노란 칸이 남는다 */
/* 줄 상자에 본문 leading이 끼면 22px 숫자가 30px 자리를 차지한다 */
.price { margin: 0 0 4px; line-height: 1; }
/* 노랑은 원가와 할인율이 붙은 딜의 표시다 (시안의 tag--hot). 우리는 원가를 수집하지
   않으므로 모든 가격이 노랑이 되어 강조가 강조를 잃는다 — 그래서 종이 위 빨강이다 */
.amount {
  font-family: var(--disp); font-weight: 400; font-size: 22px; line-height: 1.15;
  letter-spacing: -.01em; color: var(--red-text);
}
/* 원문 가격 문자열("가격다양"·"1만원대")은 시스템 한글로 그린다 — 제호체는 우리가 쓰는
   글자만 잘라 담았고 이 자리에는 남의 게시판이 쓴 아무 글자나 들어온다 (_cards.html) */
.amount.raw { font-family: var(--sans); font-weight: 700; }
.price .free { margin-left: 10px; font-weight: 700; font-size: 12px; color: var(--green); }
/* 개당 가격은 본값이 아니라 본값에서 나온 값이다 — 금액보다 작고 조용하게 둔다 */
.price .unit {
  margin-left: 10px; font-size: 12px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* 고정폭을 쓰지 않는다 — 한글이 섞인 줄이라 고정폭 스택에서는 굴림체로 떨어져
   글자가 성기게 벌어진다. 흔들리면 곤란한 건 서체가 아니라 자릿수뿐이다 */
.meta {
  margin: 0; font-size: 12px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* 시각 퍼머링크 — 딜 페이지로 가는 유일한 크롤 경로다. 메타 줄의 다른 글자와 같은
   색·크기로 두어 제목의 시선을 뺏지 않는다. 밑줄은 눌렀을 때만 */
.permalink { color: inherit; text-decoration: none; }
.permalink:hover, .permalink:focus-visible { text-decoration: underline; }
.sprite { display: none; }
/* 공유 버튼 (딜 열람·검색·공유 스펙 §4.3) — 카드 오른쪽 아래.
   메타 줄 안에 넣으면 '이력 N건' 링크와 오탭이 난다 */
/* 액션 줄의 버튼은 전부 아래 한 규칙을 쓴다. 클래스 이름은 다른 화면과 겹치면 안 된다 —
   종 버튼을 `kw`로 두었더니 알림 관리 페이지의 키워드 카드(.kw, 아래 참조)의
   padding:10px 12px와 margin-bottom:8px를 물려받아 혼자 43px이 되고 4px 위로 떠 있었다
   (2026-09-03 실측). 그래서 `kw-btn`이다 */
/* 카드 하단 액션 줄. 본문 안에 두어 썸네일 아래로 흘러들지 않게 하고, 모든 카드가
   같은 높이만큼 늘어나게 한다 — 절대 위치로 두면 제목 줄 수에 따라 버튼 위치가 카드마다
   달라진다 (2026-09-01 실측: 82px과 106px) */
/* 카드 아래 줄은 왼쪽이 버튼, 오른쪽이 메타다. DOM 순서는 메타가 먼저라
   자동 배치에 맡기지 않고 자리를 못 박는다 — 마크업 순서는 건드리지 않는다 */
/* 버튼 칸이 먼저 제 폭을 가져가고, 메타가 남는 자리를 쓰며 모자라면 줄바꿈한다.
   반대로 두면 좁은 화면에서 긴 메타가 버튼을 졸라 두 줄로 접는다 */
.card .body {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center;
}
.card .badges { grid-area: 1 / 1 / 2 / 3; }
.card h3 { grid-area: 2 / 1 / 3 / 3; }
.card .price { grid-area: 3 / 1 / 4 / 3; }
.card .actions { grid-area: 4 / 1 / 5 / 2; }
.card .meta { grid-area: 4 / 2 / 5 / 3; text-align: right; padding-left: 10px; }
/* 간격 0 + 겹친 테두리 = 이어 붙은 무리. 그림자는 버튼이 아니라 무리가 한 번만 진다 —
   버튼마다 지면 겹친 자리마다 턱이 생긴다 (시안의 .fused와 같은 규칙) */
.actions {
  display: flex; align-items: center; justify-content: flex-start;
  flex-wrap: wrap; gap: 0; margin-top: 6px;
  width: fit-content; box-shadow: 3px 3px 0 var(--ink);
}
.card .actions > * + * { margin-left: -2px; }
.card .react, .card .kw-btn, .card .share {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  padding: 0 10px; cursor: pointer; border: 2px solid var(--ink); border-radius: 0;
  background: var(--paper); color: var(--ink); font-family: inherit; font-size: 12px;
  font-weight: 700; box-shadow: none;
  transition: transform 80ms, box-shadow 80ms;
  /* 높이를 고정한다. 아이콘 크기(추천 16px, 종·공유 18px)가 높이를 정하게 두면
     같은 줄에서 2px씩 어긋난다 (2026-09-03 실측: 27.6 대 29.6) */
  min-height: clamp(34px, 10vw, 44px);
}
/* 종과 공유는 짝으로 붙는다 — 종이 '공유 왼쪽'이라는 약속이 눈에 보여야 한다.
   예전에는 auto 여백으로 둘을 오른쪽 끝에 밀어붙였고(그 여백을 공유에 주면 둘 사이가
   39px 벌어졌다), 지금은 액션 줄 전체가 테두리를 공유하는 한 줄이라 갈라질 자리가 없다 */
/* 아이콘만 있는 버튼은 정사각형이다 — 좌우 여백을 두면 무리 안에서 폭이 들쭉날쭉해진다 */
.card .kw-btn, .card .share { width: clamp(30px, 10vw, 40px); padding: 0; }
.card .react .n { min-width: 6px; font-variant-numeric: tabular-nums; }
/* 반응은 늘 보인다. 접어두던 시절에는 여는 버튼이 그것을 폈는데, 그 버튼을 없앤
   뒤로는 접어두면 되살릴 손잡이가 없다. 추천이 줄의 맨 왼쪽이고 나머지가 그 뒤를 잇는다 */
.card .react { display: flex; }
/* 무리 안에서는 한 칸만 움직일 수 없다 — 색만 바뀐다 */
.card .react:hover, .card .kw-btn:hover, .card .share:hover {
  transform: none; box-shadow: none; background: var(--yellow); color: #111;
}
/* 내가 누른 것 — 색만으로 구분하지 않도록 눌러 넣는다. 무리 전체를 옮길 수는 없으니
   칸 안쪽으로 그늘을 지운다 (시안의 .fused>.btn[aria-pressed=true]와 같다) */
.card .react[aria-pressed="true"],
.card .react[aria-pressed="true"]:hover {
  background: var(--yellow); color: #111;
  transform: none; box-shadow: inset 2px 2px 0 var(--ink);
}
.card .react:disabled { opacity: .5; cursor: default; }
/* '복사됨'은 아이콘보다 넓다 — 왼쪽으로 자라며 메타를 덮으므로 배경을 채운다 */
.card .share.done { background: var(--yellow); color: #111; }
.card .share.done svg { display: none; }
.card .share.done::after { content: "복사됨"; }
.load-more, .empty { text-align: center; color: var(--muted); padding: 20px; font-size: 13px; }
.viewing { color: var(--red-text); font-weight: 700; }

/* ---------- 전광판 티커 ('지금 많이 보는중') ----------
   마크업은 aside > h2 + ol > li > a 그대로다. app.js가 60초마다 <ol>을 통째로 다시
   채우므로 스타일은 자식이 아니라 ol 자신에 걸어야 한 바퀴 뒤에도 살아남는다.

   ol은 flex:none + width:max-content라 컨테이너보다 넓어질 수 있고, aside의
   overflow:hidden이 그것을 자른다. 라벨은 그 위를 지나가는 내용을 가려야 하므로
   z-index를 갖는다 */
.top-clicks {
  display: flex; align-items: stretch; overflow: hidden;
  /* 바탕은 토큰이 아니라 값이다. 글자가 고정 노랑(--yellow)이라 var(--ink)를 쓰면
     다크에서 바탕이 거의 흰색으로 뒤집혀 노랑 글자가 1.1:1로 사라진다.
     테두리는 토큰 그대로 둔다 — 어두운 배경 위에서 바를 떼어놓는 건 그쪽이다 */
  background: #111; border: 2px solid var(--ink); border-radius: 0;
  margin-bottom: 12px; min-height: 34px;
}
.top-clicks h2 {
  position: relative; z-index: 1; flex: none;
  display: flex; align-items: center; margin: 0 14px 0 0;
  background: var(--red); color: #fffdf7;
  font-family: var(--disp); font-size: 13px; font-weight: 400;
  padding: 0 10px; letter-spacing: -.01em; white-space: nowrap;
}
.top-clicks h2::after { content: " ▶"; }
/* 간격을 gap이 아니라 li의 오른쪽 여백으로 준다. gap이면 두 벌 사이에도 간격이 한 번
   더 끼어 '절반'이 한 벌과 어긋나고, 되돌아갈 때마다 그 차이만큼 튄다 */
.top-clicks ol {
  flex: none; display: flex; align-items: center;
  width: max-content; margin: 0; padding: 0; list-style: none;
  animation: ticker 40s linear infinite;
}
.top-clicks:hover ol, .top-clicks:focus-within ol { animation-play-state: paused; }
.top-clicks li { margin-right: 28px; font-size: 13px; white-space: nowrap; color: var(--yellow); }
.top-clicks a:hover, .top-clicks a:focus-visible { text-decoration: underline; }
.top-clicks .cnt {
  display: inline-block; min-width: 22px; text-align: center; margin-right: 6px;
  background: var(--yellow); color: #111; border-radius: 0;
  font-size: 11px; font-weight: 800; padding: 1px 4px;
  font-variant-numeric: tabular-nums;
}
@keyframes ticker { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .top-clicks { overflow-x: auto; }
  .top-clicks ol { animation: none; }
}
.float-btn {
  position: fixed; right: 18px; bottom: 18px; z-index: 20;
  min-width: 52px; width: 52px; height: 52px; padding: 0;
  border-radius: 50%; border: 3px solid var(--ink);
  background: var(--yellow); color: #111; font-size: 18px;
  box-shadow: 3px 3px 0 var(--ink);
}
.float-btn.has-new {
  width: auto; padding: 0 16px; border-radius: 999px;
  background: var(--red); color: #fffdf7; font-size: 13px; font-weight: 800;
}

/* 관리자 대시보드 */
.admin-title { font-family: var(--disp); font-size: 22px; font-weight: 400; margin: 8px 0 12px; }
main h2 { font-size: 15px; margin: 22px 0 8px; }
.notice.warn {
  background: var(--red); color: #fffdf7; padding: 8px 12px;
  border: 2px solid var(--ink); border-radius: 0; font-size: 13px;
}
.tiles { display: flex; flex-wrap: wrap; gap: 8px; }
.tile {
  flex: 1 1 150px; background: var(--card); border: 2px solid var(--ink);
  border-radius: 0; padding: 12px; display: flex; flex-direction: column; gap: 2px;
}
.tile-num { font-family: var(--disp); font-size: 24px; font-weight: 400; }
.tile-label { font-size: 12px; color: var(--muted); }
.table-wrap { overflow-x: auto; }
.cols { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-start; }
.cols .table-wrap { flex: 1 1 200px; }
.admin-table {
  width: 100%; border-collapse: collapse; background: var(--card);
  border: 2px solid var(--ink); border-radius: 0; font-size: 13px;
}
.admin-table th, .admin-table td {
  padding: 7px 10px; text-align: left; border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.admin-table th { color: var(--muted); font-weight: 700; font-size: 12px; }
.admin-table tr:last-child td { border-bottom: 0; }
.dim { color: var(--muted); font-size: 12px; }
.err { color: var(--red-text); font-size: 12px; white-space: normal; }
.status {
  padding: 2px 8px; border: 2px solid currentColor; border-radius: 0;
  font-size: 12px; font-weight: 700;
}
.status.ok { color: #0f7a4a; }
.status.warn { color: #a97400; }
.status.bad { color: var(--red-text); }
.status.off { color: var(--muted); }
.inline-form { display: inline; }
.inline-form .num {
  width: 70px; padding: 5px 6px; border: 2px solid var(--ink); border-radius: 0;
  background: var(--paper); color: var(--text); font-family: inherit;
}

/* 키워드 알림 (알림 스펙 §5.2) */
.alert-btn { margin-left: 8px; min-height: 30px; padding: 0 10px; font-size: 12px; }
.alert-sheet {
  border: 3px solid var(--ink); border-radius: 0; padding: 18px 20px;
  background: var(--card); color: var(--text); max-width: 340px; width: calc(100% - 32px);
  box-shadow: 6px 6px 0 var(--ink);
}
.alert-sheet::backdrop { background: rgb(17 17 17 / .55); }
.alert-sheet h2 { font-family: var(--disp); font-size: 17px; font-weight: 400; margin: 0 0 8px; }
.alert-sheet p { margin: 0 0 10px; font-size: 13px; line-height: 1.5; }
.alert-sheet .cond { font-weight: 700; }
.alert-sheet .muted { color: var(--muted); }
.alert-sheet .warn { color: var(--red-text); font-weight: 700; }
.alert-sheet .fineprint { font-size: 12px; color: var(--muted); margin-bottom: 12px; }
.alert-sheet .fineprint summary { cursor: pointer; }
.alert-sheet .fineprint ul { margin: 8px 0 0; padding-left: 18px; line-height: 1.7; }
.sheet-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

/* 알림 관리 (알림 스펙 §5.4) */
.alerts-page { max-width: 560px; }
.alerts-page h1 { font-family: var(--disp); font-size: 22px; font-weight: 400; margin: 8px 0 14px; }
.alerts-page h2 { font-size: 14px; margin: 22px 0 6px; }
.alerts-page h2.danger { color: var(--red-text); }
.alerts-page .muted { color: var(--muted); font-size: 13px; line-height: 1.6; margin: 0 0 8px; }
.alerts-page .row { display: flex; align-items: center; gap: 6px; font-size: 13px; margin: 6px 0; }
.kw-list { list-style: none; padding: 0; margin: 0 0 8px; }
.kw {
  background: var(--card); border: 2px solid var(--ink); border-radius: 0;
  padding: 10px 12px; margin-bottom: 8px;
}
.kw-main { font-size: 14px; margin-bottom: 6px; }
.kw-edit { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 12px; }
.kw-edit input {
  width: 96px; padding: 6px; border-radius: 0; font-size: 12px;
  border: 2px solid var(--ink); background: var(--paper); color: var(--text);
}
.kw-edit button { min-height: 30px; padding: 0 10px; font-size: 12px; }
.kw-edit .note { margin-left: auto; }
.danger-btn { color: var(--red-text); border-color: var(--red-text); box-shadow: 3px 3px 0 var(--red-text); }
.danger-btn:hover { background: var(--red); color: #fffdf7; box-shadow: 4px 4px 0 var(--ink); }
.kw-edit .dd summary { font-size: 12px; }
.kw-edit .dd-menu label.dd-item { display: flex; gap: 6px; align-items: center; cursor: pointer; }
.kw-edit select {
  padding: 6px; border-radius: 0; font-size: 12px; font-family: inherit;
  border: 2px solid var(--ink); background: var(--paper); color: var(--text);
}

/* 딜 페이지 (딜 열람·검색·공유 스펙 §4.3) */
.deal-page { max-width: 720px; }
/* 피드에서는 #deals가 종이 시트이고 카드는 그 위의 한 줄이다. 딜 페이지에는 그 시트가
   없어서 카드가 맨 바탕에 앉는다 — 크림색 .noimg가 크림색 배경에 묻히고, 잘라내기
   점선과 ✂이 있지도 않은 다음 줄을 약속한다. 기준 카드가 스스로 종이가 된다.
   그림자는 주지 않는다: 키워드 상자가 카드 바로 아래에 이어 붙어 이음매에 그늘이 낀다.
   테두리를 낱개로 쓰는 이유는 왼쪽을 비워 카테고리 띠(border-left-color)를 살리기 위해서다 */
.deal-page > .card {
  background: var(--paper);
  border-top: 3px solid var(--ink); border-right: 3px solid var(--ink);
  border-bottom: 3px solid var(--ink);
}
/* 종을 누르면 키워드 상자가 카드 바로 아래에 이어 붙는다 — 그때만 밑변을 연다.
   피드에서 #deals가 하는 이음매 처리를 여기서는 카드가 혼자 한다 */
.deal-page > .card:has(+ .kw-box) { border-bottom: 0; }
.deal-page > .card::after { display: none; }
.deal-sub { font-family: var(--disp); font-size: 15px; font-weight: 400; margin: 22px 0 8px; }
.copy-fallback { display: flex; gap: 8px; align-items: center; font-size: 12px; }
.copy-fallback input {
  flex: 1; padding: 7px 8px; border-radius: 0; font-size: 12px;
  border: 2px solid var(--ink); background: var(--paper); color: var(--text);
}

/* 읽음 상태 (딜 열람·검색·공유 스펙 §4.1) — 제목·메타만 옅게.
   가격·배지·이미지는 선명하게 둔다. 종료 카드의 opacity와 겹쳐 너무 흐려지지 않게 */
.card.read h3 a, .card.read .meta { opacity: .55; }
.card.read h3 a { font-weight: 400; }
/* 딜 페이지의 기준 카드는 흐리지 않는다. 페이지를 열면 그 자리에서 열람으로 기록되는데,
   `원문 보기` 버튼을 없앤 뒤로는 제목이 원문으로 나가는 유일한 링크다 — 도착하자마자
   유일한 행동이 흐려져 보이면 안 된다 */
.deal-page .card.read h3 a, .deal-page .card.read .meta { opacity: 1; }
.deal-page .card.read h3 a { font-weight: 700; }
.badge.just-seen { background: var(--red); color: #fffdf7; border-color: var(--ink); }

/* 레이트리밋 안내 */
.notice { max-width: 520px; margin: 40px auto; text-align: center; }
.notice h1 { font-family: var(--disp); font-size: 22px; font-weight: 400; margin: 0 0 12px; }
.notice p { font-size: 14px; line-height: 1.7; margin: 0 0 10px; }
.notice .dim { color: var(--muted); font-size: 13px; }
.notice .primary-link { margin-top: 10px; min-height: 44px; padding: 0 20px; font-size: 14px; }

/* 카드 키워드 칩 패널 (카드 키워드 칩 설계 §3). 카드 다음 형제로 끼워지므로
   카드와 같은 폭·모서리를 쓰고, 위쪽 모서리만 붙여 한 덩어리로 보이게 한다 */
.kw-box {
  /* -8px는 .card의 margin-bottom과 정확히 같다 — 붙여야 한 덩어리로 읽힌다 */
  margin: 0; padding: 10px 12px 12px;
  background: var(--card); border: 2px solid var(--ink); border-top: 0;
  border-left-width: 6px; border-radius: 0;
}
.kw-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.kw-picked { flex: 1; min-width: 0; font-size: 13px; font-weight: 700; overflow-wrap: anywhere; }
.kw-picked:empty::before { content: "단어를 골라주세요"; font-weight: 400; color: var(--muted); }
.kw-go { background: var(--ink); color: var(--paper); }
.kw-close { padding: 0 10px; }
.kw-chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* 칩은 기존 필터 칩 모양을 그대로 쓴다 — 같은 제스처를 같은 모양으로 */
.kw-chips .chip { border: 2px solid var(--ink); cursor: pointer; font: inherit; font-size: 13px; font-weight: 700; }
.kw-foot { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.kw-input {
  flex: 1; min-width: 0; padding: 8px 10px; border: 2px solid var(--ink); border-radius: 0;
  background: var(--paper); color: var(--text); font: inherit; font-size: 13px;
  box-shadow: inset 2px 2px 0 rgb(17 17 17 / .12);
}
/* 왜 잠겼는지 — 버튼 옆에 붙여야 읽고 바로 고친다 */
.kw-why { flex: 1; min-width: 0; color: var(--red-text); font-size: 12px; text-align: right; }
.kw-alert { margin-left: auto; }

/* Black Han Sans는 굵기가 400 하나뿐이다. 800을 요구하면 브라우저가 합성 볼드를 얹어
   이미 검은 획을 뭉갠다 — 디스플레이체를 쓰는 자리는 굵기를 요구하지 않는다.
   폰트가 늦거나 막히면 var(--sans)로 떨어지는데, 그때도 크기와 자간이 위계를 만든다 */
.logo, .price, .noimg, .top-clicks h2, .admin-title, .tile-num,
.alert-sheet h2, .alerts-page h1, .deal-sub, .notice h1 {
  font-weight: 400;
}

/* ---------- 좁은 화면: 상자는 화면을 꽉 채운다 ----------
   main의 좌우 여백을 0으로 만드는 대신 상자만 그 밖으로 흘려보낸다. 여백을 지우면
   칩 줄의 -12px 블리드가 화면 밖으로 밀리고 '표시할 딜이 없습니다' 같은 글이
   가장자리에 붙는다.

   오른쪽 테두리는 지운다 — 화면 끝에 붙은 2px 선은 경계가 아니라 흠집으로 읽힌다.
   왼쪽 띠는 남긴다. 카드와 그 아래 패널을 한 덩어리로 잇는 표시다.

   이 블록은 파일 끝에 있어야 한다. .kw-box의 margin이 단축 속성이라, 앞에 두면
   그쪽이 나중에 나와서 여기 longhand를 덮는다 — 미디어쿼리는 특이도를 올려주지 않는다 */
@media (max-width: 767px) {
  /* 딜 행과 그 패널은 #deals가 이미 화면 끝까지 나가므로 스스로 밀지 않는다 —
     둘 다 밀면 -12가 두 번 겹쳐 가로로 24px 넘친다 (실측 388 > 375) */
  .top-clicks { margin-left: -12px; margin-right: -12px; border-right: 0; }
  .card h3 { font-size: 16px; }  /* 시안도 좁은 화면에서 한 단계 내린다 */
  .history-box, .kw-box { border-right: 0; }
}

/* ---------- 좁은 화면의 딜 행 ----------
   여기서도 카드는 격자다. flex로 두면 썸네일 링크가 행 높이만큼 늘어나(align-items가
   stretch라) 이미지 아래로 빈 상자가 깔리고, 버튼 줄이 그 오른쪽 칸에 갇혀 왼쪽까지
   내려오지 못한다. 시안은 버튼이 썸네일 아래에서 시작한다.

   .body를 display:contents로 풀어 그 자식들이 카드의 칸을 직접 차지한다 —
   마크업은 그대로다 */
@media (max-width: 767px) {
  .card {
    display: grid; align-items: start; column-gap: 12px;
    grid-template-columns: var(--thumb) auto minmax(0, 1fr);
  }
  .card .body { display: contents; }
  .card .thumb { grid-area: 1 / 1 / 4 / 2; }
  .card .badges { grid-area: 1 / 2 / 2 / 4; }
  .card h3 { grid-area: 2 / 2 / 3 / 4; }
  .card .price { grid-area: 3 / 2 / 4 / 4; }
  .card .actions { grid-area: 4 / 1 / 5 / 3; }
  .card .meta {
    grid-area: 4 / 3 / 5 / 4; text-align: right; align-self: center; padding-left: 8px;
  }
}
/* 폰에서는 버튼 다섯 개와 긴 메타가 한 줄에 못 앉는다 (259px 안에 178+177) —
   메타가 제 줄을 갖고 버튼은 그 아래 폭 전체를 쓴다 */
@media (max-width: 479px) {
  /* 세 칸을 유지하면 가운데 칸이 잡아줄 내용을 잃고 뭉개진다 — 메타도 버튼도 폭 전체를
     쓰므로 어느 칸도 붙들지 않는다. 두 칸으로 줄인다 */
  .card { grid-template-columns: var(--thumb) minmax(0, 1fr); }
  .card .badges { grid-area: 1 / 2 / 2 / 3; }
  .card h3 { grid-area: 2 / 2 / 3 / 3; }
  .card .price { grid-area: 3 / 2 / 4 / 3; }
  .card .meta {
    grid-area: 4 / 1 / 5 / 3; text-align: left; padding-left: 0; align-self: start;
  }
  .card .actions { grid-area: 5 / 1 / 6 / 3; }
}

/* ---------- 좁은 화면: 상자는 화면을 꽉 채운다 ----------
   main의 좌우 여백을 0으로 만드는 대신 상자만 그 밖으로 흘려보낸다. 여백을 지우면
   칩 줄의 -12px 블리드가 화면 밖으로 밀리고 '표시할 딜이 없습니다' 같은 글이
   가장자리에 붙는다.

   오른쪽 테두리는 지운다 — 화면 끝에 붙은 2px 선은 경계가 아니라 흠집으로 읽힌다.
   왼쪽 띠는 남긴다. 카드와 그 아래 패널을 한 덩어리로 잇는 표시다.

   이 블록은 파일 끝에 있어야 한다. .kw-box의 margin이 단축 속성이라, 앞에 두면
   그쪽이 나중에 나와서 여기 longhand를 덮는다 — 미디어쿼리는 특이도를 올려주지 않는다 */
@media (max-width: 767px) {
  /* 딜 행과 그 패널은 #deals가 이미 화면 끝까지 나가므로 스스로 밀지 않는다 —
     둘 다 밀면 -12가 두 번 겹쳐 가로로 24px 넘친다 (실측 388 > 375) */
  .top-clicks { margin-left: -12px; margin-right: -12px; border-right: 0; }
  .card h3 { font-size: 16px; }  /* 시안도 좁은 화면에서 한 단계 내린다 */
  .history-box, .kw-box { border-right: 0; }
}

/* 좁은 화면에서는 메타가 제 줄을 갖는다.
   375px에서 본문에 남는 폭은 259px인데, 반응이 붙은 카드의 버튼 줄이 178px,
   '13분 전 · 9명 보는중 · 이력 3건'이 177px이다. 한 줄에 못 앉는다 — 억지로 앉히면
   메타가 글자 하나씩 접히거나(칸 40px) 버튼이 세 줄로 접힌다. 둘 다 실측했다.
   넓은 화면에서는 나란히 선다 (위 그리드 규칙) */

/* ---------- 넓은 화면: 시안의 5열 조판 ----------
   썸네일 | 내용(배지·제목·메타) | 가격 딱지 | 버튼 블록.
   .body를 display:contents로 풀어 그 자식들이 카드의 그리드 칸을 직접 차지한다 —
   마크업을 건드리지 않고 다섯 칸을 만드는 유일한 방법이다. .body는 의미 없는 div라
   보조기술에서 잃을 역할도 없다 */
@media (min-width: 768px) {
  .card {
    display: grid; align-items: center;
    grid-template-columns: var(--thumb) minmax(0, 1fr) 200px auto;
    column-gap: 14px;
  }
  .card .body { display: contents; }
  .card .thumb { grid-area: 1 / 1 / 4 / 2; }
  .card .badges { grid-area: 1 / 2 / 2 / 3; }
  .card h3 { grid-area: 2 / 2 / 3 / 3; margin-bottom: 0; }
  .card .meta { grid-area: 3 / 2 / 4 / 3; text-align: left; padding-left: 0; margin-top: 4px; }

  /* 가격은 오른쪽에 붙이는 딱지다. 왼쪽 가장자리의 구멍은 뜯어 쓰는 표를 흉내낸다 —
     뒤가 종이색 카드라 크림색 원이 뚫린 자리로 읽힌다 */
  .card .price {
    grid-area: 1 / 3 / 4 / 4; margin: 0; position: relative; text-align: left;
    border: 2px solid var(--ink); box-shadow: 3px 3px 0 var(--ink);
    background: var(--paper); padding: 10px 12px 10px 18px;
  }
  .card .price::before {
    content: ""; position: absolute; left: -7px; top: 50%;
    width: 12px; height: 12px; margin-top: -6px; border-radius: 50%;
    background: var(--cream); border: 2px solid var(--ink);
  }
  .card .price .amount { display: block; }
  .card .price .free { display: block; margin: 4px 0 0; margin-left: 0; }
  /* 가격이 없는 딜(이벤트·나눔)에는 빈 딱지를 세우지 않는다 */
  .card .price:not(:has(.amount)):not(:has(.free)) { border: 0; box-shadow: none; padding: 0; }
  .card .price:not(:has(.amount)):not(:has(.free))::before { display: none; }

  .card .price .amount { font-size: 30px; }

  /* 시안의 투표 블록: 추천은 56×44에 화살표 위 숫자 아래로 쌓이고, 비추천은 그 절반
     높이의 한 줄이다. 종·공유는 그 아래 28×30 둘이 나란히 선다 */
  .card .actions {
    grid-area: 1 / 4 / 5 / 5; margin-top: 0;
    display: grid; grid-template-columns: repeat(2, 28px); gap: 0;
  }
  .card .actions > * { margin: 0; }
  .card .react { grid-column: 1 / -1; padding: 0; }
  .card .react[data-value="recommend"] {
    min-height: 44px; flex-direction: column; gap: 1px; font-size: 14px;
  }
  .card .react[data-value="unrecommend"] {
    min-height: 24px; margin-top: -2px; font-size: 11px; gap: 3px;
  }
  .card .react[data-value="recommend"] svg { width: 14px; height: 14px; }
  .card .react[data-value="unrecommend"] svg { width: 11px; height: 11px; }
  .card .kw-btn, .card .share { width: 28px; min-height: 30px; margin-top: -2px; }
  .card .share { margin-left: -2px; }
}

/* ---------- 딜 목록은 한 장의 종이다 ----------
   시안에서 행은 상자가 아니라 시트 위의 줄이다 — 배경도 테두리도 없고 왼쪽 띠와 절취선만
   갖는다. 그래서 종이 노릇은 목록이 한다. 행 사이 간격이 0이 되었으므로 카드에 이어
   붙는 패널의 -8px 당김도 함께 거뒀다 (그 값은 카드 margin-bottom과 짝이었다) */
#deals {
  background: var(--paper); border: 3px solid var(--ink);
  box-shadow: 6px 6px 0 var(--ink);
}
@media (max-width: 767px) {
  #deals { border: 0; box-shadow: none; margin-left: -12px; margin-right: -12px; }
}

/* ---------- 카테고리 띠 ----------
   왼쪽 띠는 위 카테고리 탭과 같은 색을 쓴다. 탭에서 '파랑 = 전자/IT'를 배운 눈이
   목록에서 그 색을 다시 만난다 — 그래서 --tint 정의를 칩 전용으로 두지 않고 풀었다.
   기타는 칩에서 크림색인데, 종이 위 띠로는 보이지 않아 여기서만 흐린 잉크로 바꾼다 */
.card { border-left-color: var(--tint, var(--ink)); }
.card[data-cat="etc"] { border-left-color: var(--muted); }

/* ---------- 상세 페이지로 가는 스트립 ----------
   카드 오른쪽 끝을 세로로 쓴다. 격자가 폭마다 열 수를 바꾸므로 칸을 하나 더 만드는 대신
   절대 위치로 얹고, 카드 오른쪽 여백으로 그 자리를 비워둔다 — 세 벌의 열 정의를
   건드리지 않아도 된다. 위아래 12px은 카드 안쪽 여백과 같아 절취선을 넘지 않는다 */
.card { padding-right: 34px; }
.card .detail {
  position: absolute; right: 0; top: 12px; bottom: 12px; width: 30px;
  display: flex; align-items: center; justify-content: center;
  border-left: 2px dashed var(--ink); color: var(--muted);
}
.card .detail:hover, .card .detail:focus-visible { background: var(--yellow); color: #111; }

/* ---------- 페이지가 바뀔 때 ----------
   카테고리를 누르면 서버가 새 문서를 준다. 탭에 트랜지션을 걸어도 문서가 갈리는 순간
   사라지므로, 문서 사이를 브라우저가 이어 붙이게 한다. 양쪽 문서가 모두 이 선언을
   가질 때만 동작한다 — /out/ 다리는 공통 CSS를 안 쓰므로 여기 걸리지 않는다.
   지원하지 않는 브라우저에서는 지금과 똑같이 즉시 바뀐다 */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}
