/* 스포츠매치매치 (드림즈 지정 스포츠클럽). 공통 스타일 (모바일 우선 · 고령 회원 기준)
 *
 * 기준 사용자는 **돋보기를 쓰고 폰을 한 손에 든 파크골프 회원**이다. 젊은 사용자는
 * 큰 글씨를 불편해하지 않지만 그 반대는 성립하지 않으므로, 전 화면을 이 기준에 맞춘다.
 *
 *   - 본문 17px, 버튼/입력 글씨 19px. **여기서 더 줄이지 말 것.** 15px 로 되돌리면
 *     이 클럽의 주 사용자층이 읽지 못한다.
 *   - 누르는 것은 최소 48px, 주 동작은 60px 이상. 장갑 낀 손, 운동장 햇빛, 떨리는 손가락.
 *   - 한 화면에 주 동작 버튼은 하나. 아이콘만 있는 버튼을 만들지 않는다(전부 한글 글자를 붙인다).
 *   - 숨은 제스처, 2단 메뉴, 롱프레스 없음. 보이는 것만 누르면 된다.
 *   - 종목은 색으로 먼저 구분된다(글자를 읽기 전에 알아채야 한다).
 *   - **390px 과 360px 두 폭에서 한 손으로 쓸 수 있어야 한다.** 가로 스크롤·고정 너비 금지.
 *   - 동의·약관 문구도 예외가 아니다. **개인정보 동의 문구는 본문 크기로 읽힌다**
 *     (10px 회색 약관 글씨 금지). 자세한 규칙은 아래 "개인정보 동의 (v1.10.0)" 절.
 *
 * ---------------------------------------------------------------------------
 * v1.17.0: 색·글씨·여백은 여기에 없다. `public/css/tokens.css` 한 곳에 있다.
 * ---------------------------------------------------------------------------
 *
 * 이 파일과 소개 홈페이지(home.css)가 **같은 토큰 파일**을 읽는다. 그래서 두 화면이
 * 한 제품으로 보이고, 색을 바꾸는 일이 한 줄 수정이 된다. 아래에서 `#`으로 시작하는
 * 색을 새로 적지 말 것 — 토큰에 없으면 토큰에 추가하고 여기서는 var() 로 쓴다.
 * (예외는 배지 몇 개의 아주 연한 바탕뿐이고, 그것도 대비를 재서 넣은 값이다.)
 *
 * 화면에서 실제로 재는 검사는 `node scripts/ui-gate.js` 다. 글씨 14px 미만,
 * 48px 미만 누름 자리, 4.5:1 미만 대비, 360/390px 가로 스크롤을 전부 잡아낸다.
 */

@font-face {
  font-family: "Pretendard KO";
  src: url("/static/fonts/pretendard-ko-var.woff2?v=1.0.0") format("woff2-variations"),
       url("/static/fonts/pretendard-ko-var.woff2?v=1.0.0") format("woff2");
  font-weight: 300 800;
  font-style: normal;
  font-display: swap;
}


* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  word-break: keep-all;
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
h1, h2, h3, h4 { margin: 0; word-break: keep-all; letter-spacing: -0.3px; }
:focus-visible { outline: 3px solid var(--turf); outline-offset: 2px; }

.num { font-variant-numeric: tabular-nums; }

/* 글자 없이 그림만 있는 표시. **혼자 뜻을 지지 않는다** — 옆이나 아래에 반드시 한글
   글자가 붙어 있고, 이건 그 글자를 거드는 것이다. 색은 글자색을 그대로 따라간다.
   **이모지를 쓰지 않는다.** 기기마다 다른 그림이 나오고(안드로이드 구버전에서는 네모),
   크기와 색을 제어할 수 없어 대비를 보장할 수 없다. v1.17.0 에서 전부 걷어냈다. */
.i { display: inline-block; width: 1em; height: 1em; flex: 0 0 auto; vertical-align: -0.14em; }
.i svg { display: block; width: 100%; height: 100%; }

/* ------------------------------------------------------------------ 머리띠
 *
 * **v1.17.0 에서 초록 → 숯색.** 되돌리지 말 것.
 * 머리띠가 초록이면 화면에서 제일 큰 초록 덩어리가 "누를 수 없는 띠" 가 되고, 정작
 * 눌러야 하는 [신청하기] 초록 버튼이 두 번째가 된다. 머리띠를 숯색으로 내리면 이 앱에서
 * 초록은 **누르는 것** 한 가지 뜻만 갖는다. 숯색은 대한체육회 화면의 그 계열이고,
 * 흰 글씨 대비 15.61:1 이다. */

.appbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--bar);
  color: #fff;
  padding: 0 var(--s4);
  padding-top: env(safe-area-inset-top);
  height: calc(var(--bar-h) + env(safe-area-inset-top));
  display: flex;
  align-items: center;
  gap: var(--s2);
}
/* 클럽 이름 자체가 홈으로 가는 링크다. 글자만 30px 높이였다 — 누르는 것은 48px 이다. */
.appbar .brand { font-weight: 800; font-size: var(--fs-control); letter-spacing: -0.5px; display: flex; align-items: center; gap: var(--s2); min-width: 0; min-height: var(--tap); }
.appbar .brand .bn { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 360px 폰에서만 한 단계 줄인다. v1.23.0 에서 클럽 이름이 "드림즈 스포츠클럽"(19px 에서
   131px)에서 **"지정스포츠클럽 드림즈"(163px)** 로 길어졌는데, 360px 머리띠가 이름에
   내주는 자리는 152px 이라 말줄임표로 잘렸다(실측). 17px 이면 146px 이라 다 들어간다.
   **17px 은 이 저장소의 본문 바닥이다. 더 줄이지 말 것** — 그 밑은 어르신 화면에서 못 읽는다.
   390px 이상은 자리가 남아 19px 그대로다(폰 두 폭 다 실제 화면에서 확인했다). */
@media (max-width: 379px) {
  .appbar .brand { font-size: 17px; }
}
/* 클럽 표장. 옆에 늘 클럽 이름이 붙어 있으므로 이 네모가 혼자 뜻을 지지 않는다. */
.appbar .brand .mark {
  width: 30px; height: 30px; border-radius: var(--r-sm); background: var(--turf); color: #fff;
  display: grid; place-items: center; flex: 0 0 auto;
}
.appbar .brand .mark .i { width: 18px; height: 18px; }
.appbar .spacer { flex: 1; }
/* 예전 값(13.5px · rgba(255,255,255,.82) = 4.02:1)은 글씨 바닥도 대비 기준도 못 넘었다.
   지금은 15px 이고 숯색 바탕에서 9.87:1 이다. */
.appbar .who { font-size: var(--fs-sm); color: var(--bar-ink); text-align: right; line-height: 1.25; }
.appbar .who b { display: block; color: #fff; font-weight: 700; font-size: var(--fs-base); }
/* 누르는 것은 전부 48px 이상. 예전에는 42px 이었다(엄지 하나로 못 맞춘다). */
.appbar .icon-btn {
  background: var(--bar-2); border: 1px solid var(--bar-line); color: #fff; border-radius: var(--r-sm);
  min-width: var(--tap); min-height: var(--tap); font-size: var(--fs-base); font-weight: 700;
  font-family: inherit; cursor: pointer; padding: 0 var(--s3);
  display: inline-flex; align-items: center; justify-content: center;
}
.appbar .icon-btn:active { background: var(--bar-line); }
.appbar .back {
  background: var(--bar-2); border: 1px solid var(--bar-line); border-radius: var(--r-sm);
  min-width: var(--tap); min-height: var(--tap); display: grid; place-items: center; color: #fff;
}
.appbar .back .i { width: 22px; height: 22px; }
/* v1.63.0, 머리띠의 카톡 심볼 버튼(views/partials/share-site.ejs 의 `logo` 모양).
   대표님이 「그냥 카카오로고누르면」 이라고 말씀하신 그 자리다. 카톡 공식 노란 바탕
   (#FEE500)에 카톡 갈색 심볼(#191919). 카카오 로그인 버튼(`.kakao-action`)과 같은 색이라
   이 앱에서 노랑은 「카카오」 한 가지 뜻만 갖는다(머리띠 숯색 위 12.5:1, 심볼은
   노랑 위 15.4:1). 크기는 `.appbar .icon-btn` 의 누름 바닥(48px)과 맞춘다.
   글자가 없는 유일한 버튼이므로 이름은 markup 의 aria-label 이 갖는다.

   v1.65.0 에서 폭을 40px -> 48px 로 고쳤다. **이 줄은 바닥 위반이었다.** v1.63.0 이
   「심볼 하나분이라 좁아도 된다」고 40px 로 두었는데, 이 저장소의 누름 바닥은
   48x48 이고(--tap) `scripts/ui-gate.js` 가 그것을 잰다. 그 판은 gate 를 다시
   돌리지 않아 넘어갔고, 이번 판에서 스테이징 두 대로 확인했더니 **v1.64.0 에서도
   화면 58개 중 36건이 전부 이 버튼 하나** 때문이었다(`누름 40x48`). 실측으로
   48px 이 안전한 것도 확인했다: 로그아웃 머리띠(`/privacy` 360px)는 여백
   24px -> 16px 로 줄기만 하고, 로그인 머리띠는 `.spacer` 가 이미 0 이라 이름칸
   `.bn` 이 93px -> 87px 가 된다. `.bn` 은 말줄임 칸이고 360px 에서는 40px 일 때도
   이미 잘려 있었다(text 145px), 즉 잘림은 이 8px 이 만든 것이 아니다. 가로
   스크롤은 320/360/390px 모두 없다. **다시 40px 로 줄이지 말 것.** */
.appbar .kakao-logo-btn {
  flex: 0 0 auto;
  width: var(--tap); min-height: var(--tap);
  border: 0; border-radius: var(--r-sm); background: #fee500; color: #191919;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; padding: 0;
}
.appbar .kakao-logo-btn:active { background: #f4dc00; }
.appbar .kakao-logo-btn .i { width: 22px; height: 22px; }

.subbar {
  position: sticky; top: calc(var(--bar-h) + env(safe-area-inset-top)); z-index: 39;
  background: var(--card); border-bottom: 1px solid var(--line);
  display: flex; gap: var(--s2); padding: 10px 12px; overflow-x: auto; scrollbar-width: none;
}
.subbar::-webkit-scrollbar { display: none; }
.chip {
  flex: 0 0 auto; padding: 10px 18px; border-radius: var(--r-sm); font-size: 16px; font-weight: 700;
  background: var(--bg); color: var(--ink-2); border: 1px solid var(--line); min-height: var(--tap);
  display: inline-flex; align-items: center; gap: 5px;
}
.chip.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.chip .n { font-size: var(--fs-xs); color: inherit; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ 종목 고르기 (v1.6.0)
 *
 * 홈의 종목 고르기다. **가로 스크롤 칩 띠를 대체한 것이라 되돌리지 말 것.**
 * 종목이 일곱이 되면서 390px 화면에서 넷째부터가 화면 밖으로 밀렸고 스크린파크골프는
 * 글자가 반만 보였다. 어르신 화면에서 반만 보이는 칸은 **없는 칸**이다.
 *
 * 규칙 셋:
 *   1. 일곱 칸이 **전부 한 화면에** 보인다. 가로 스크롤도, 펼치기도, 시트도 없다.
 *      어느 종목이든 누르는 횟수는 언제나 한 번이다.
 *   2. 라벨을 줄이지 않는다. "필드파크골프" 는 여섯 글자 그대로 두 줄로 나온다
 *      (줄 나눔 자리는 src/lib/sports.ts 의 SPORT_TILE_LINES 가 정한다).
 *   3. 타일 하나가 64px 이상. 두 칸 격자라 폭도 넉넉해서 엄지로 못 누를 일이 없다.
 *
 * sticky 가 아니다. 일곱 칸을 화면 위에 붙박아 두면 경기 목록이 영구히 밀린다.
 * 스크롤로 이 격자가 사라지면 .sportnow 띠가 대신 붙는다.
 */
/* ---------------------------------------- 메인 스폰서 **큰창** (v1.54.0) — 홈 맨 위 한 곳
 *
 * 대표님 말: 「젤위에 메인 스폰서 들어갈 수있게 **큰창**으로 하나만들어주시면감사」.
 *
 * ### 왜 한 번 다시 만들었나
 *
 * 처음 판은 로고 썸네일 옆에 이름을 나란히 놓은 **한 줄 띠**(높이 100px 남짓)였다.
 * 그건 큰창이 아니라 이름표다. 대표님이 보내 온 참고 화면들도 맨 위가 **큰 그림**이다.
 * 지금은 **그림이 판이다** - 가로를 꽉 채우는 그림 상자 안에 후원사 로고·현수막이
 * 들어가고, 표시와 이름은 그 위/아래에 붙는다.
 *
 * ### 아래 6칸 후원사 띠와 다른 부품이다
 *
 * 같은 `.sponsor` 카드를 키워서 쓰지 않았다. 저 카드는 **가입된 팀 띠가 같이 쓰는**
 * 부품이고(`views/partials/teams.ejs`), 무엇보다 이 판은 **돌지 않는다** - 대표님이
 * 고른 한 곳이 5초 만에 사라지면 광고가 아니라 고장이다. 회전 부품(`data-rotator`)을
 * 여기에 붙이지 말 것.
 *
 * ### 자르지도 늘리지도 않는다 (object-fit: contain)
 *
 * 후원사가 올리는 것은 가로로 긴 현수막·모집 포스터 사진과, 작은 정사각 로고 두 가지다.
 * 정사각 로고를 가로 상자에 `cover` 로 채우면 위아래가 잘려 나가고, 억지로 늘리면
 * 뭉개진다. 그래서 **비율을 그대로 두고 상자 안에 맞춘다** - 현수막은 가로를 꽉 채우고,
 * 정사각 로고는 가운데에 선다. 남는 자리는 옅은 바탕이라 두 경우 다 자연스럽다.
 * `cover` 로 바꾸지 말 것.
 *
 * ### 높이를 **고정**으로 잡은 이유 (비율 상자가 아니라)
 *
 * 처음에는 `aspect-ratio: 2/1` 로 잡았다. 그런데 운영에 실제로 올라와 있는 후원사
 * 그림 네 장을 재어 보니 **전부 세로로 긴 사진**이었다(480px 높이에 폭 224~360.
 * 2026-09-18 실측). 후원사가 올리는 것은 가로로 긴 배너가 아니라 **현수막·모집 포스터를
 * 찍은 세로 사진**과 정사각 로고다. 비율을 2:1 로 못 박으면 그 세로 사진이 폭 516px
 * 상자 안에서 172x258 로 쪼그라들어 **양옆에 344px 의 빈 자리**가 남는다 - 큰창이
 * 아니라 액자다.
 *
 * 그래서 높이를 정하고(`240px`) 그림은 그 안에 맞춘다. 세로 사진은 높이를 꽉 채우고,
 * 가로 배너는 폭을 꽉 채운다. 둘 다 잘리지 않고(contain) 상자가 그림에 맞춰 흔들리지도
 * 않는다(높이가 고정이라 아래 종목 격자가 밀리지 않는다).
 *
 * 240px 인 근거: 좌우 여백을 뺀 폭 516px, 이름 줄 50px, 판 전체 302px, 머리띠 56px ->
 * 358px 이라 **종목 격자의 첫 줄이 540px 폰의 접힘선 위에 그대로 남는다.** 대표님이
 * 홈에서 제일 먼저 보는 것이 종목 타일이므로 큰창이 그것을 밀어내면 안 된다.
 * 이 값을 키우려면 그 숫자를 먼저 재 볼 것.
 */
.mainspon-wrap { background: var(--card); padding: 12px 12px 0; }
.mainspon {
  display: block; overflow: hidden;
  background: #fff; border: 1px solid var(--line-2); border-radius: var(--r-lg);
  text-decoration: none; color: inherit;
}
/* 누를 수 있는 판만 손가락 반응을 준다(주소 없는 후원사는 div 라 반응이 없다 -
   눌러도 아무 일이 없는 자리를 어르신 화면에 만들지 않는다). */
a.mainspon:active { background: var(--card-2); }
/* v1.76.1: 「메인스폰서. 그림더 크게 들어갈수있게. 메인스폰서라는 단어는 화면에 안나타나도됩니다」
   고정 높이 상자(240/300px)를 버리고 **그림을 판의 폭 전체로** 그린다. 높이는 그림 비율대로
   따라간다(자르지도 늘리지도 않는다). 서버가 흰 여백을 잘라 낸 `*.main.jpg` 를 주므로
   (sponsors.ts ensureMainVariant) 사진 자체가 판을 채운다. 아주 긴 세로 사진이 화면을
   통째로 덮지 않도록 높이만 화면의 75%(최대 640px)로 묶는다.
   [메인 스폰서] 글씨 띠는 지웠다(대표님 요청). */
/* v1.88.0 fix (「사이즈가 자동으로 안줄어요」 2026-10-12): a tall portrait upload (app-icon screenshot on
   gray) rendered at full height, pushing the name and the 이전/다음 pager under the bottom tabs.
   The picture now always sits in a FIXED box (16:9, max 220px high on phones, 340px from 768px),
   object-fit: contain, centered on a neutral gray, whatever size or shape he uploads. */
.mainspon .shot {
  position: relative; display: flex; align-items: center; justify-content: center; overflow: hidden;
  width: 100%; aspect-ratio: 16 / 9; max-height: 220px; background: #f3f4f6;
}
/* v1.90.0 대표님 「아무사진넣으면 꽉차지는않나요」 / 「네」: the box is always FULL. Default is cover
   (centered; a tall poster loses some top/bottom, a wide banner some sides). Per sponsor
   「사진 전체 보이기」 (Sponsor.fitWhole) adds .whole: the picture is contained and a blurred,
   enlarged copy of the same file fills the rest, so nothing is cut and there are no gray bars.
   Box caps from v1.88 stay (16:9, 220px phone, 340px from 768px). */
.mainspon .shot img {
  display: block; width: 100%; height: 100%; max-height: none;
  object-fit: cover; object-position: center;
}
.mainspon .shot.whole img { position: relative; z-index: 1; object-fit: contain; }
.mainspon .shot.whole img.bg {
  position: absolute; inset: -8%; z-index: 0; width: 116%; height: 116%;
  object-fit: cover; filter: blur(18px) saturate(1.1) brightness(.9); transform: translateZ(0);
}
/* 그림이 없는 후원사(첫 글자만 크게)는 예전 크기의 자리를 그대로 쓴다. */
.mainspon .shot .ini {
  display: grid; place-items: center; width: 100%; height: 100%;
  font-size: 64px; font-weight: 800; color: var(--turf-dark);
  background: linear-gradient(180deg, var(--card-2) 0%, #eef2f0 100%);
}
.mainspon .tx { display: flex; flex-direction: column; gap: 2px; padding: 10px 14px 12px; }
.mainspon .nm { font-size: var(--fs-lg); font-weight: 800; line-height: 1.25; word-break: keep-all; }
/* 「홈페이지 바로가기」 는 **누를 수 있다는 것을 글자로** 말하는 자리다(아이콘은 이 제품의
   금지 사항이고, 폰에는 hover 가 없다). 주소를 넣은 후원사에만 선다. */
.mainspon .go { font-size: var(--fs-sm); font-weight: 800; color: var(--turf-dark); text-decoration: underline; text-underline-offset: 3px; }
.mainspon .ln { font-size: var(--fs-sm); color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 데스크톱. 대표님 실효 폭이 1097px 이다(1920 모니터 175% 확대. NOTES 8ac).
   넓은 화면에서 높이는 조금만 키운다 - 1097px 폭에 그림이 가로로 꽉 차면 세로 사진은
   가운데에 서고 좌우 여백만 넓어질 뿐, 판을 더 키울 이유가 없다. */
@media (min-width: 768px) {
  .mainspon .shot { max-height: 340px; }
  .mainspon .nm { font-size: var(--fs-xl); }
  .mainspon .tx { padding: 12px 18px 14px; }
}

/*
 * v1.55.0 — 큰 판이 **돌아가는 상자**가 되었다(대표님 「돌아가게 3개쩜 부탁」).
 *
 * 회전 부품은 하나다(`.rotbox` + public/js/rotator.js). 그래서 이 section 에 `rotbox`
 * 클래스가 붙고, 위쪽 `.rotbox` 규칙(칸 폭 · 스크롤 판 · [이전]/[다음] · 5초 간격)을
 * 그대로 물려받는다. 여기서 덮는 것은 **회전 부품이 아니라 이 자리(맨 위)의 사정** 두 가지뿐이다.
 *
 *  1. `.rotbox { margin-top: 26px }` 를 0 으로. 이 판은 화면 맨 위라 위 여백이 붙으면
 *     머리띠 아래가 벌어진다. 회전 부품 전체에 손대지 않고 이 자리에서만 끈다 -
 *     `.rotbox` 자체를 고치면 아래 6칸 띠·가맹점·협력기관이 전부 같이 움직인다.
 *  2. 아래 여백. `.mainspon-wrap` 은 `padding: 12px 12px 0` 이라 판 아래가 0 이다.
 *     [이전]/[다음] 줄이 판 아래에 서므로 그 줄까지 감싸는 여백이 필요하다.
 *
 * `--per`(칸 수)는 **여기서 덮지 않는다.** 기본값 1 이라 한 판에 한 곳이고, 그게 이
 * 부품의 뜻이다("한 판에 셋" 이 아니다. 셋이 **돌아가며** 나온다). `multi` 도 붙이지 않는다.
 */
.mainspon-wrap.rotbox { margin-top: 0; padding-bottom: 12px; }
/* 판 아래 [이전]/[다음] 줄의 좌우를 판(12px)과 맞춘다. */
.mainspon-wrap.rotbox .rot-nav { margin: 10px 2px 0; }

.sportpick {
  background: var(--card); border-bottom: 1px solid var(--line);
  padding: 12px; display: grid; gap: 8px;
}
.sportpick .sp-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
/* v1.31.0: **바탕은 화면 끝까지, 내용은 본문 폭까지.**
 *
 * 이 띠에는 폭 상한이 아예 없었다. 그래서 1920px 에서 종목 칸 하나가 944x147 이 되고
 * 그 안의 사진이 921x78 짜리 가로 띠로 잘려 나갔다(`.pic.photo` 가 cover 다).
 * 대표님 사진에서 파크골프 사진이 이상하게 길쭉한 이유가 이것이다.
 *
 * 좌우 여백으로 묶는다(`max-width` + `margin:auto` 가 아니라). 그래야 **회색 바탕과
 * 아래 실선은 화면 끝까지 이어지고** 안의 칸만 본문과 줄이 맞는다. 머리띠 · 부제 띠와
 * 같은 방법이라 셋이 한 세로줄로 선다. `max()` 는 좁은 화면에서 12px 을 지키는 바닥이다.
 *
 * 칸 수는 폭을 따라 올라간다. 여섯 종목이라 1024px 부터 **한 줄에 여섯 칸**이고, 이건
 * 이 절의 원래 약속("일곱 칸이 전부 한 화면에, 누르는 횟수는 언제나 한 번")을 넓은
 * 화면에서 더 잘 지킨다. **폰(767px 이하)은 두 칸 그대로다 — 손대지 않았다.** */
@media (min-width: 768px) {
  .sportpick {
    padding-left: max(12px, calc((100% - var(--app-max)) / 2));
    padding-right: max(12px, calc((100% - var(--app-max)) / 2));
  }
  .sportpick .sp-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .sportpick .sp-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
.sportpick .sp-all,
.sportpick .sp-tile {
  border: 1.5px solid var(--line); border-radius: var(--r); background: var(--bg);
  color: var(--ink); font-weight: 800; font-size: var(--fs-base); line-height: 1.22;
}
.sportpick .sp-all {
  display: flex; align-items: center; justify-content: center; gap: var(--s2);
  padding: 10px 12px; min-height: var(--btn-h);
}
/*
 * 종목 타일. **v1.12.0 에서 커졌고 그림이 들어갔다.**
 *
 * 고객 말 그대로: "종목별 박스가 좀작은데 그곳에 종목별 이미지를 넣어두면 좋을듯합니다".
 * 64px 한 줄짜리 칸을 **그림(위) + 이름(아래)** 두 층으로 바꾸고 높이를 두 배로 키웠다.
 * 순서를 뒤집지 말 것. 어르신은 그림으로 먼저 찾고 이름으로 확인한다.
 *
 * **두 칸 격자는 그대로 둔다.** 종목이 일곱이라 한 줄에 셋을 넣으면 360px 에서
 * "스크린파크골프" 가 다시 잘린다(v1.6.0 에서 겪은 그 문제다). 세 칸으로 늘리지 말 것.
 */
.sportpick .sp-tile {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 9px 8px 11px; min-height: 128px; text-align: center;
}
.sportpick .pic { display: block; width: 100%; height: 66px; }
/* contain 이라 그림이 절대 찌그러지지 않는다(타일 폭은 화면 폭에 따라 달라진다). */
.sportpick .pic img { display: block; width: 100%; height: 100%; object-fit: contain; }
/*
 * 실사 사진일 때만 붙는 규칙 (v1.15.0).
 *
 * 손그림(SVG)은 자기 배경을 갖고 있어서 여백 안에 `contain` 으로 놓아야 모양이 산다.
 * 사진은 반대다. `contain` 으로 두면 66px 안에 우표만 하게 들어가서 무슨 사진인지
 * 알아볼 수 없다. 그래서 사진은 타일 폭을 **꽉 채우고(cover)** 모서리를 둥글게 깎는다.
 * 이 두 가지를 한 가지로 합치지 말 것. 합치면 한쪽이 반드시 망가진다.
 */
/* ★v1.46.0: 사진 칸을 **사진에 맞는 비율**로 바꿨다.★
 *
 * 대표님: **「종목 사진이 좀 가려져서 수정」**
 *
 * ### 무엇이 가리고 있었나 (실측)
 *
 * 덮는 것(뱃지·그라데이션·글자 띠)은 없었다. **칸 비율이 범인**이다.
 * 클럽이 올린 종목 사진은 여섯 장 전부 거의 정사각이다(480x358 ~ 480x417, 비율 1.15~1.34).
 * 그런데 사진 칸은 `height: 78px` 고정이라 타일 폭을 따라 **가로로 길어지기만** 했다:
 *
 *   390px  타일 156px → 칸 156x78  = 비율 2.00 → 원본 세로의 **57.6%만 남고 42% 가 잘림**
 *   1097px 타일 149px → 칸 149x78  = 비율 1.91 → 세로 60.2% (**40% 가 잘림**)
 *
 * `cover` 는 모자란 쪽을 **가운데만 남기고 위아래를 버린다.** 그래서 스크린파크골프
 * 사진에서 **두 분의 얼굴(위)과 발·공(아래)** 이 통째로 사라졌다. 대표님이 「가려져서」
 * 라고 하신 것이 이것이다.
 *
 * ### 고친 방법
 *
 * 칸 **높이**를 고정하지 않고 **비율(4:3)** 을 고정한다. 그러면 타일이 넓든 좁든 칸
 * 비율이 1.333 로 일정하고, 원본(1.15~1.34)과 가까워 세로로 **10% 남짓**만 잘린다.
 * 스크린파크골프 기준 원본 417px 중 361px 이 남아 얼굴도 발도 그대로 있다.
 * `max-width` 는 넓은 화면에서 칸이 다시 가로로 늘어나는 것을 막는 바닥이다
 * (칸이 넓어지면 비율이 아니라 **폭**이 커져 같은 사고가 돌아온다).
 *
 * ★칸 높이만 재고 「고쳤다」 하지 말 것.★ v1.29.0 에서 정확히 그렇게 했다가 대표님께
 * 「그대로인데요」를 들었다. 칸은 가지런해졌는데 `object-fit` 이 그 안에서 따로 놀아
 * **칠해진 그림**은 전과 같았기 때문이다. 이 판은 `scripts/measure-v1460.js` 로
 * `naturalWidth/Height` 와 칸 비율에서 **실제로 남는 세로 비율**을 계산해 확인했다.
 *
 * `object-position` 을 가운데에서 위로 올리지 않았다. 4:3 에서는 위아래가 고르게 조금씩만
 * 빠지고, 위로 올리면 이번엔 발이 잘린다(여섯 장 모두 사람이 서 있는 사진이다). */
.sportpick .pic.photo {
  height: auto; aspect-ratio: 4 / 3; max-width: 160px; margin: 0 auto;
  border-radius: var(--r-sm); overflow: hidden; background: var(--line-2);
}
.sportpick .pic.photo img { object-fit: cover; }
.sportpick .t { min-width: 0; }
/* 경기 수. 라벨을 밀어내지 않는 자리에 둔다.
   **v1.18.0: 알약(999px) → 각진 8px.** .badge 는 이미 v1.17.0 에서 각을 살렸는데
   이 칸만 알약으로 남아 있었다. 같은 화면 안에서 모양이 두 가지면 회원은 그 차이에
   뜻이 있다고 읽는다(없다). */
.sportpick .n {
  font-size: var(--fs-xs); font-weight: 800;
  min-width: 26px; height: 26px; padding: 0 6px; border-radius: var(--r-sm);
  background: var(--ink); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-variant-numeric: tabular-nums;
}
.sportpick .sp-all .n { margin-left: 2px; }
/* 타일에서는 그림 위 오른쪽 구석. 그림과 겹쳐도 읽히도록 테두리를 한 겹 두른다. */
.sportpick .sp-tile .n { position: absolute; top: 7px; right: 7px; box-shadow: 0 0 0 2.5px var(--card); }
.sportpick .sp-tile.on .n { box-shadow: none; }
/* 고르지 않은 타일도 종목 색을 왼쪽 띠로 보여 준다. 글자를 읽기 전에 색으로 먼저 안다. */
.sportpick .sp-tile { border-left-width: 6px; }
.sportpick .sp-tile.sport-parkgolf   { border-left-color: var(--sp-parkgolf); }
.sportpick .sp-tile.sport-screenpark { border-left-color: var(--sp-screenpark); }
.sportpick .sp-tile.sport-baseball   { border-left-color: var(--sp-baseball); }
.sportpick .sp-tile.sport-soccer     { border-left-color: var(--sp-soccer); }
.sportpick .sp-tile.sport-futsal     { border-left-color: var(--sp-futsal); }
.sportpick .sp-tile.sport-badminton  { border-left-color: var(--sp-badminton); }
/* 고른 칸은 그 종목 색으로 통째로 칠한다. 테두리만 바꾸면 어르신 화면에서 안 보인다. */
.sportpick .on { color: #fff; border-color: transparent; }
.sportpick .on .n { background: #fff; color: var(--ink); box-shadow: none; }
.sportpick .sp-all.on { background: var(--ink); }
.sportpick .sp-tile.on.sport-parkgolf   { background: var(--sp-parkgolf); }
.sportpick .sp-tile.on.sport-screenpark { background: var(--sp-screenpark); }
.sportpick .sp-tile.on.sport-baseball   { background: var(--sp-baseball); }
.sportpick .sp-tile.on.sport-soccer     { background: var(--sp-soccer); }
.sportpick .sp-tile.on.sport-futsal     { background: var(--sp-futsal); }
.sportpick .sp-tile.on.sport-badminton  { background: var(--sp-badminton); }

/* 격자가 화면 밖으로 나갔을 때만 나타나는 얇은 띠. 지금 무엇으로 걸러 보고 있는지
   계속 알려 주고, 누르면 격자로 되돌아간다. 이게 sticky 인 유일한 줄이다(48px). */
.sportnow {
  position: sticky; top: calc(var(--bar-h) + env(safe-area-inset-top)); z-index: 39;
  background: var(--card); border-bottom: 1px solid var(--line); padding: 6px 12px;
}
.sportnow[hidden] { display: none; }
.sportnow .sn-btn {
  width: 100%; min-height: var(--tap); border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--bg); font-family: inherit; font-size: var(--fs-base); font-weight: 800; color: var(--ink);
  display: flex; align-items: center; gap: var(--s2); padding: 0 var(--s3); cursor: pointer;
}
.sportnow .k { font-size: var(--fs-xs); font-weight: 700; color: var(--ink-2); }
.sportnow .v { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sportnow .go { margin-left: auto; font-size: var(--fs-sm); font-weight: 800; color: var(--turf-dark); text-decoration: underline; text-underline-offset: 3px; }

/* 보기 범위(모집중 / 내가 신청한 / 지난). 같은 이유로 가로 스크롤을 뗐다. */
/* 비로그인이면 칸이 둘뿐이라 grid 대신 flex 로 나눈다(빈 칸이 남지 않는다). */
.scopepick { display: flex; gap: 8px; margin-bottom: 12px; }
.scopepick a {
  flex: 1 1 0; min-width: 0; min-height: var(--ctl-h); border-radius: var(--r-sm); border: 1.5px solid var(--line); background: var(--card);
  color: var(--ink-2); font-size: var(--fs-base); font-weight: 800; line-height: 1.2; text-align: center;
  display: flex; align-items: center; justify-content: center; padding: 6px 4px;
}
.scopepick a.on { background: var(--ink); color: #fff; border-color: var(--ink); }

/* ------------------------------------------------------------------ 레이아웃
 *
 * ---------------------------------------------------------------------------
 * v1.31.0: **로그인한 회원 앱의 폭.** 소개 홈페이지만 넓히고 여기를 빼먹었었다.
 * ---------------------------------------------------------------------------
 *
 * 2026-09-10 01:45 KST, 대표님이 그대로 적어 보내신 말: **「아직까지 화면이
 * 꽉차지않아요」 「일반회원들이 보는화면. 컴터로」**. 보내신 사진은 소개 홈페이지가
 * 아니라 **로그인한 회원 앱**이었다. 우리는 v1.21.0 에서 `.pub`(home.css)만 넓혔고,
 * `.wrap`(이 파일)은 640px 에 그대로 있었다. 그래서 1920px 모니터에서 이렇게 됐다
 * (실측, evidence/v1310/measure-member-before.txt):
 *
 *     본문(.wrap)   640px   → 뷰포트의 33.3%,  좌우로 1280px 이 빈다
 *     종목 띠       1896px  → **본문의 3배**. 폭 상한이 아예 없었다.
 *     종목 칸       944x147 → 두 칸 격자가 폭만 늘어나서 사진이 921x78 띠가 됐다
 *
 * 한 화면 안에서 종목 띠는 화면을 다 먹고 그 아래 본문은 640px 이었다. 대표님 사진에
 * 보이는 것이 정확히 이것이다. **둘 중 하나만 고치면 안 된다** — 띠만 묶으면 본문이
 * 여전히 좁고, 본문만 넓히면 띠가 여전히 화면을 다 먹는다.
 *
 * ### `--app-max` 한 곳에서 정한다
 *
 * 머리띠 · 종목 띠 · 본문 · 하단 탭 · 푸터가 **같은 값**을 읽는다. 예전에는 640 이라는
 * 숫자가 여섯 군데에 흩어져 있었고, 그래서 한 군데만 고쳐지는 날이 왔다(지금이 그날이다).
 * 여기 말고 다른 곳에 폭 숫자를 다시 적지 말 것.
 *
 * 값을 고른 근거는 **채움비**다. 고정 픽셀 상한 하나로는 1280 에서 맞으면 1920 에서
 * 빈다. 그래서 두 단이고, 어느 데스크톱 폭에서나 80~92% 가 차게 잡았다
 * (1280→92% · 1440→82% · 1920→81%). 소개 홈페이지(1440 에서 100%, 1920 에서 91.7%)와
 * 같은 계열로 읽힌다. **폰(360·390)은 이 단에 들어오지도 않는다 — 640px 상한이 그대로다.**
 */
:root { --app-max: 640px; }
@media (min-width: 768px)  { :root { --app-max: 1180px; } }
@media (min-width: 1600px) { :root { --app-max: 1560px; } }

.wrap {
  max-width: var(--app-max);
  margin: 0 auto;
  padding: var(--wrap-pad) var(--wrap-pad) calc(var(--tab-h) + 28px + env(safe-area-inset-bottom));
}
/* 표가 많은 운영자 화면. 예전에는 본문(640)보다 넓히려고 1080 을 적었는데, 이제 본문이
   1180 까지 가므로 그냥 1080 을 쓰면 **운영자 화면이 회원 화면보다 좁아진다.** 둘 중
   큰 쪽을 쓴다. 여기에 고정 숫자를 도로 적지 말 것. */
.wrap.wide { max-width: max(1080px, var(--app-max)); }
/* 하단 고정 [신청하기] 가 있는 화면은 그 버튼 높이만큼 더 비워 둔다.
   비워 두지 않으면 본문 마지막 줄(명단 끝, 후원사 마지막 칸)이 버튼에 영영 가린다. */
.wrap.has-cta { padding-bottom: calc(var(--tab-h) + 130px + env(safe-area-inset-bottom)); }
.stack { display: grid; gap: 12px; }

.page-title { font-size: var(--fs-xl); font-weight: 800; margin: var(--s2) 0 var(--s1); line-height: var(--lh-tight); }
.page-sub { color: var(--ink-2); font-size: var(--fs-base); margin-bottom: var(--s3); }

/* 카드는 **그림자로 띄우지 않고 실선 한 겹으로 가른다.** 떠 있는 카드가 겹겹이 쌓이면
   어느 것이 지금 화면의 본문인지 흐려진다(공공기관 화면이 카드를 띄우지 않는 이유이기도
   하다). 그림자를 다시 넣지 말 것. */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s4);
}
.card.tight { padding: var(--s3); }
.card h3 { font-size: var(--fs-md); font-weight: 800; margin-bottom: var(--s3); }
.card + .card { margin-top: var(--s3); }

/* 구역 머리. 왼쪽 굵은 세로선 한 줄이 "여기서부터 새 구역" 을 말한다.
   대한체육회 화면에서 가져온 유일한 표시이고, 장식이 아니라 구분선이다. */
.sect-head { display: flex; align-items: center; gap: var(--s2); margin: var(--s6) 2px var(--s3); }
.sect-head h2 {
  font-size: var(--fs-lg); font-weight: 800; line-height: var(--lh-tight);
  border-left: 4px solid var(--ink); padding-left: 10px;
}
/* [더보기] 도 누르는 것이다. 예전 6px 세로 여백으로는 24px 짜리 자리였다. */
.sect-head .more {
  margin-left: auto; font-size: var(--fs-base); color: var(--turf-dark); font-weight: 700;
  min-height: var(--tap); display: inline-flex; align-items: center; padding: 0 var(--s2);
  text-decoration: underline; text-underline-offset: 4px;
}

/* ------------------------------------------------------------------ 경기 카드 */

.match-card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  padding: var(--s4) var(--s4) var(--s4) var(--s5); display: block; position: relative; overflow: hidden;
}
.match-card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 6px; background: var(--ink-3); }
/* v1.65.0: 카드 전체가 링크(matches.ejs 의 `a.match-card`)인데 눌린 표시가 없었다.
   바탕을 칠하면 종목 색 띠와 싸우므로 테두리만 진해진다. */
a.match-card:active { border-color: var(--ink-3); }
.match-card.sport-parkgolf::before { background: var(--sp-parkgolf); }
.match-card.sport-baseball::before { background: var(--sp-baseball); }
.match-card.sport-soccer::before { background: var(--sp-soccer); }
.match-card.sport-futsal::before { background: var(--sp-futsal); }
.match-card.sport-badminton::before { background: var(--sp-badminton); }
.match-card.sport-screenpark::before { background: var(--sp-screenpark); }
.match-card .row1 { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; }
.match-card .ttl { font-size: var(--fs-lg); font-weight: 800; margin-bottom: var(--s2); line-height: var(--lh-tight); }
.match-card .meta { display: grid; gap: 5px; font-size: var(--fs-base); color: var(--ink-2); }
.match-card .meta .k { display: inline-block; width: 56px; color: var(--ink-3); font-size: var(--fs-sm); font-weight: 700; }
.match-card .foot { display: flex; align-items: center; gap: 8px; margin-top: 11px; padding-top: 11px; border-top: 1px solid var(--line-2); }
.match-card .cap { font-size: var(--fs-base); font-weight: 800; font-variant-numeric: tabular-nums; }
.match-card .cap .of { color: var(--ink-3); font-weight: 500; }
/* v1.75.0 photo banner (「구장사진 한장 넣을수있나요. 종목별도 마찮가지고요」).
   Venue photo, else sport photo; a card with neither has no .mc-photo and is unchanged.
   Inset and rounded (not edge-bleed) so it holds under every card padding variant below. */
.match-card .mc-photo {
  display: block; height: 128px; margin-bottom: 10px; border-radius: 10px; overflow: hidden;
  background: var(--card-2);
}
.match-card .mc-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.md-photo {
  height: 180px; margin-bottom: 14px; border-radius: 12px; overflow: hidden; background: var(--card-2);
}
.md-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.md-photo.vh-photo { height: 150px; margin-bottom: 10px; }
@media (max-width: 380px) {
  .match-card .mc-photo { height: 112px; }
  .md-photo { height: 160px; }
}

/* 알약(999px) 에서 각진 표로 바꿨다. 공공기관 화면의 그 상태 표시이고, 종목색이
   글자와 테두리 양쪽에 걸려 있어 색을 못 보는 사람도 글자로 읽는다.
   글씨는 14px 에서 15px 로 올렸다(이 배지가 종목을 알려 주는 유일한 글자다). */
.badge {
  display: inline-flex; align-items: center; gap: 4px; padding: 5px 10px; border-radius: 6px;
  font-size: var(--fs-sm); font-weight: 800; border: 1px solid transparent; white-space: nowrap;
}
.badge.sport-parkgolf { color: var(--sp-parkgolf); background: var(--sp-parkgolf-bg); border-color: var(--sp-parkgolf); }
.badge.sport-baseball { color: var(--sp-baseball); background: var(--sp-baseball-bg); border-color: var(--sp-baseball); }
.badge.sport-soccer   { color: var(--sp-soccer);   background: var(--sp-soccer-bg);   border-color: var(--sp-soccer); }
.badge.sport-futsal   { color: var(--sp-futsal);   background: var(--sp-futsal-bg);   border-color: var(--sp-futsal); }
.badge.sport-badminton { color: var(--sp-badminton); background: var(--sp-badminton-bg); border-color: var(--sp-badminton); }
.badge.sport-screenpark { color: var(--sp-screenpark); background: var(--sp-screenpark-bg); border-color: var(--sp-screenpark); }
.badge.ok { color: var(--turf); background: var(--turf-bg); border-color: var(--turf-line); }
.badge.wait { color: var(--hold); background: var(--hold-bg); border-color: var(--hold-line); }
.badge.bad { color: var(--warn); background: var(--warn-bg); border-color: var(--warn-line); }
.badge.sim { color: var(--sim); background: var(--sim-bg); border-color: var(--sim-line); }
.badge.mute { color: var(--ink-2); background: var(--bg); border-color: var(--line); }

.pill-time { margin-left: auto; font-size: var(--fs-sm); color: var(--ink-2); font-weight: 700; }

/* 정원 게이지. 글자가 아니라 **뜻이 있는 그림 요소**라 3:1 이상이 필요하다.
   찬 만큼(초록)과 빈 자리가 둘 다 보여야 해서 테두리를 두르고 높이를 12px 로 올렸다.
   옆에 늘 "12 / 16" 숫자가 같이 있으므로 이 막대만으로 정보를 지지는 않는다. */
.gauge { flex: 1; height: 12px; border-radius: 3px; background: var(--card-2); border: 1px solid var(--line); overflow: hidden; }
.gauge > i { display: block; height: 100%; background: var(--turf); }
.gauge.full > i { background: var(--warn); }

/* ------------------------------------------------------------------ 버튼 */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: var(--btn-h); padding: 0 var(--s5); border-radius: var(--r); border: 1.5px solid var(--line);
  background: var(--card); color: var(--ink); font-family: inherit; font-size: var(--fs-control); font-weight: 800;
  cursor: pointer; text-align: center;
}
/* 눌린 표시는 **색만 바꾼다.** v1.18.0 전에는 translateY(1px) 로 버튼이 움직였다.
   no-slop-ui 의 Motion 규칙(변형·바운스·스케일 금지, 색·테두리·그림자만)에 걸리고,
   여기서는 접근성 이유가 하나 더 있다: 손이 떨리는 회원에게 눌린 순간 버튼이 1px
   움직이면 "빗나갔나" 로 읽혀 한 번 더 누르게 된다. 되돌리지 말 것. */
.btn:active { background: var(--card-2); }
.btn.primary { background: var(--turf); border-color: var(--turf); color: #fff; }
/* 눌림 색이 대비를 떨어뜨리지 않는다. 흰 글씨가 7.61 → 9.00:1 로 오히려 올라간다. */
.btn.primary:active { background: var(--turf-dark); border-color: var(--turf-dark); }
.btn.danger:active { background: var(--warn-bg); }
.btn.ghost:active { background: var(--card-2); }
/* 못 누르는 주 버튼을 흐린 회색으로 두지 않는다. 숯색을 그대로 쓰고 아래 :disabled 의
   투명도만 걸린다(그래도 흰 글씨가 4.5:1 을 넘는다). */
.btn.primary:disabled { background: var(--ink-2); border-color: var(--ink-2); }
.btn.danger { background: var(--card); border-color: var(--warn); color: var(--warn); }
.btn.ghost { background: transparent; border-color: var(--line); color: var(--ink-2); }
.btn.block { width: 100%; }
/* 작은 버튼도 48px 아래로 못 내려간다. 그게 이 제품의 바닥이다. */
.btn.sm { min-height: var(--tap); padding: 0 var(--s4); font-size: var(--fs-base); border-radius: var(--r-sm); }
.btn:disabled { opacity: .62; cursor: not-allowed; }
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; }
.btn-row > * { flex: 1; }

/* 화면 하단에 붙는 주 동작 (경기 상세의 "신청하기")
 *
 * **v1.18.0 에서 반투명 + blur(8px) 을 걷어냈다.** 유리판(glassmorphism)은 이번에 받은
 * 기준(no-slop-ui)의 하드 금지이고, 여기서는 실제 손해가 둘 더 있었다:
 *   1. 뒤 본문이 비쳐서 [신청하기] 둘레의 대비가 스크롤 위치마다 달라졌다.
 *      (밝은 카드 위에서와 진한 명단 줄 위에서 같은 버튼이 다르게 보인다.)
 *   2. backdrop-filter 는 이 클럽 회원이 실제로 쓰는 구형 안드로이드에서 스크롤마다
 *      다시 합성된다. 명단이 눈에 띄게 끊긴다.
 * 불투명 한 겹으로 되돌렸다. 다시 반투명으로 만들지 말 것. */
.sticky-cta {
  position: fixed; left: 0; right: 0; bottom: calc(var(--tab-h) + env(safe-area-inset-bottom));
  z-index: 35; background: var(--card);
  border-top: 1px solid var(--line); padding: 12px 14px calc(12px);
}
.sticky-cta .inner { max-width: var(--app-max); margin: 0 auto; display: flex; gap: 9px; align-items: center; }

/* ------------------------------------------------------------------ 폼 */

label.f { display: block; margin-bottom: 18px; }
label.f > span.l { display: block; font-size: var(--fs-base); font-weight: 800; color: var(--ink); margin-bottom: var(--s2); }
/* 도움말도 본문이다. 회색 약관 글씨로 낮추지 말 것. */
label.f > span.hint { display: block; font-size: var(--fs-sm); color: var(--ink-2); margin-top: 7px; line-height: 1.5; }

input[type=text], input[type=tel], input[type=password], input[type=number],
input[type=date], input[type=time], input[type=datetime-local], input[type=search], select, textarea {
  width: 100%; min-height: var(--btn-h); padding: 14px 16px; border: 1.5px solid var(--line);
  border-radius: var(--r); background: var(--card); color: var(--ink);
  font-family: inherit; font-size: var(--fs-control); /* iOS 확대 방지 + 고령 회원 가독성. 16px 미만으로 절대 내리지 말 것 */
}
textarea { min-height: 110px; resize: vertical; line-height: 1.5; }
select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%235c636c' stroke-width='1.8' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 14px center; padding-right: 36px; }
input:focus, select:focus, textarea:focus { border-color: var(--turf); outline: none; box-shadow: 0 0 0 3px var(--turf-bg); }

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.grid2 > label.f { margin-bottom: 0; }
/* v1.75.0: grid children default to min-width:auto, so a date/time input whose intrinsic
   width is wider than half a 360px phone pushed the page sideways (/matches/new was 391px
   wide at 360). Let the columns shrink and the inputs follow them. Same fix covers every
   form that pairs date/time inputs in .grid2 (me, admin-events, match-new). */
.grid2 > * { min-width: 0; }
input[type=date], input[type=time], input[type=datetime-local], input[type=month] { min-width: 0; max-width: 100%; }

/* 체크박스를 눌러 고르는 큰 타일 (종목 선택) */
.pickers { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
.pick { position: relative; }
.pick input { position: absolute; opacity: 0; pointer-events: none; }
.pick span {
  display: flex; align-items: center; justify-content: center; gap: var(--s2); min-height: 68px;
  border: 2px solid var(--line); border-radius: var(--r); background: var(--card);
  font-weight: 800; font-size: var(--fs-control); color: var(--ink-2); text-align: center; padding: 8px 10px;
}
.pick input:checked + span { border-color: var(--turf); background: var(--turf-bg); color: var(--turf-dark); }
.pick input:focus-visible + span { outline: 3px solid var(--turf); outline-offset: 2px; }

.radio-row { display: flex; gap: 8px; }
.radio-row .pick { flex: 1; }

/* ------------------------------------------------------------------ 알림 배너 */

.flash { border-radius: var(--r); padding: 15px 17px; font-size: var(--fs-base); font-weight: 700; margin-bottom: var(--s4); line-height: 1.5; display: flex; gap: var(--s2); align-items: flex-start; border-left-width: 5px; border-left-style: solid; }
.flash .i { width: 22px; height: 22px; margin-top: 1px; }
.flash.ok { background: var(--turf-bg); color: var(--turf-dark); border: 1px solid var(--turf-line); }
.flash.bad { background: var(--warn-bg); color: var(--warn); border: 1px solid var(--warn-line); }
/* 알림 배너 색. 파랑은 종목색(축구)과 같은 계열이라 --sp-soccer 를 그대로 쓴다.
   토큰에 없는 색을 여기서 새로 만들지 않는다. */
.flash.info { background: var(--sp-soccer-bg); color: var(--sp-soccer); border: 1px solid var(--sp-soccer); }
/* 사진 용량 안내처럼 "무엇을 다시 하면 되는지" 를 반드시 읽혀야 하는 경우.
   이 화면의 운영자는 폰을 한 손에 들고 운동장 햇빛 아래에서 본다. 17px 로 되돌리지 말 것. */
.flash.lg { font-size: var(--fs-lg); padding: 18px 18px; line-height: 1.5; }
/* "회원화면으로 확인" (v1.72.0). Sits directly under an admin success message and reads as one
   unit with it: the message loses its bottom gap, the button is full width and at least the
   big button height so a thumb on a phone cannot miss it. */
.flash.ok.with-go { margin-bottom: var(--s2); }
.btn.flash-go { margin-bottom: var(--s4); min-height: 64px; padding: 10px var(--s4); line-height: 1.3; }
.btn.flash-go .i { width: 24px; height: 24px; flex: none; }
.flash-go-t { display: flex; flex-direction: column; align-items: flex-start; text-align: left; }
.btn.admin-go-member { margin: var(--s2) 0 var(--s2); min-height: 68px; padding: 10px var(--s4); line-height: 1.3; }
.btn.admin-go-member .i { width: 26px; height: 26px; flex: none; }
.flash-go-t small { font-size: var(--fs-sm); font-weight: 700; opacity: .92; }

/* 모의 동작 안내. 실제로 나가지 않았다는 사실을 절대 숨기지 않는다. */
.sim-note {
  border: 1px dashed var(--sim); background: var(--sim-bg); color: var(--sim);
  border-radius: var(--r); padding: 14px 16px; font-size: var(--fs-base); font-weight: 700; line-height: 1.55;
}
.sim-note b { font-weight: 800; }

/* 발송이 거절된 줄 (v1.7.0).
 * 알림톡 실패는 조용하다. 회원은 아무것도 못 받았는데 명단에는 신청이 멀쩡히 있다.
 * 그래서 실패한 줄은 목록 안에서 **색으로 먼저** 눈에 띄어야 하고, 실패 사유는
 * 작은 회색 글씨가 아니라 본문 크기로 적는다. **회색으로 낮추지 말 것.** */
.list .li.failrow { background: var(--warn-bg); border-left: 6px solid var(--warn); }
.failwhy {
  margin-top: var(--s2); padding: 10px 12px; border-radius: var(--r-sm);
  background: var(--card); border: 1px solid var(--warn); color: var(--warn);
  font-size: var(--fs-base); font-weight: 700; line-height: 1.5;
}
.failwhy b { font-weight: 800; }

/* ------------------------------------------------------------------ 목록 · 표 */

.list { background: var(--card); border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.list .li { display: flex; align-items: center; gap: 13px; padding: 16px 16px; min-height: 66px; border-bottom: 1px solid var(--line-2); }
.list .li:last-child { border-bottom: 0; }
.list .li .grow { flex: 1; min-width: 0; }
.list .li .t { font-weight: 700; font-size: var(--fs-md); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.list .li .s { font-size: var(--fs-sm); color: var(--ink-2); margin-top: 3px; }
.list .li .amt { font-weight: 800; font-size: var(--fs-md); font-variant-numeric: tabular-nums; white-space: nowrap; }
.list .li .amt.plus { color: var(--turf-dark); }
.list .li .amt.minus { color: var(--warn); }
/* 목록 왼쪽의 네모 한 칸. **예전에는 이모지 한 글자였다.**
   지금은 종목 이름 첫 글자(파·스·야·축·풋·배)이고, 종목색을 그대로 입는다.
   기기마다 다른 그림이 나오던 문제가 사라졌고, 색을 못 보는 사람도 글자로 읽는다. */
.list .li .avatar {
  width: 46px; height: 46px; border-radius: var(--r-sm); background: var(--bg); color: var(--ink);
  display: grid; place-items: center; font-weight: 800; font-size: var(--fs-md); flex: 0 0 auto;
  border: 1.5px solid var(--line);
}
.list .li .avatar .i { width: 24px; height: 24px; }
.list .li .avatar.sport-parkgolf   { background: var(--sp-parkgolf-bg);   color: var(--sp-parkgolf);   border-color: var(--sp-parkgolf); }
.list .li .avatar.sport-screenpark { background: var(--sp-screenpark-bg); color: var(--sp-screenpark); border-color: var(--sp-screenpark); }
.list .li .avatar.sport-baseball   { background: var(--sp-baseball-bg);   color: var(--sp-baseball);   border-color: var(--sp-baseball); }
.list .li .avatar.sport-soccer     { background: var(--sp-soccer-bg);     color: var(--sp-soccer);     border-color: var(--sp-soccer); }
.list .li .avatar.sport-futsal     { background: var(--sp-futsal-bg);     color: var(--sp-futsal);     border-color: var(--sp-futsal); }
.list .li .avatar.sport-badminton  { background: var(--sp-badminton-bg);  color: var(--sp-badminton);  border-color: var(--sp-badminton); }
.list .li.rank .avatar { background: var(--bg); color: var(--ink-2); font-variant-numeric: tabular-nums; font-size: var(--fs-base); }
/* 숫자 뒤에 붙는 단위(명 · 개 · 건 · P). 15px, --ink-2 는 흰 바탕 8.81:1.
   숫자보다 조용하되 "무엇의 숫자인지" 를 읽을 수 있는 크기다(14px 바닥 위). */
.list .li .amt small { font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2); margin-left: 3px; }

/* 빈 화면. 예전에는 42px 짜리 이모지 한 글자가 앉아 있었다(기기마다 다른 그림이 나온다).
   지금은 글자만 남기고, 안내문을 본문보다 한 단계 크게 세운다. */
.empty { text-align: center; padding: 52px 20px; color: var(--ink-2); font-size: var(--fs-md); line-height: 1.6; }
.empty .big { display: block; width: 46px; height: 46px; margin: 0 auto var(--s3); color: var(--ink-3); }

/* 표는 이 제품에서 예외 취급이다. 운영자 화면에만 있고, 좁은 화면에서는 글씨를 줄이는
   대신 **이 상자 안에서만** 가로로 민다(문서 전체가 가로로 밀리지 않는다). */
.tbl-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: var(--r); border: 1px solid var(--line); background: var(--card); max-width: 100%; }
table.tbl { width: 100%; border-collapse: collapse; font-size: var(--fs-base); min-width: 560px; }
table.tbl th, table.tbl td { padding: 14px 13px; border-bottom: 1px solid var(--line-2); text-align: left; white-space: nowrap; }
/* 표 머리글. --fs-xs(14px) 는 이 제품의 절대 바닥이고, 표 머리는 그 바닥을 쓰는 유일한 자리다.
   내용 칸(td)은 본문 크기 그대로다. 대한체육회 화면처럼 표를 12px 로 채우지 않는다. */
table.tbl th { background: var(--card-2); font-size: var(--fs-xs); color: var(--ink-2); font-weight: 800; position: sticky; top: 0; letter-spacing: 0.2px; }
table.tbl td.n, table.tbl th.n { text-align: right; font-variant-numeric: tabular-nums; }
table.tbl tr:last-child td { border-bottom: 0; }
table.tbl tbody tr:active { background: var(--card-2); }
table.tbl tr.total td { font-weight: 800; background: var(--card-2); border-top: 2px solid var(--ink); }

/* --- 넘치지 않는 표 (`.tbl.fitwide`) — v1.33.0 --------------------------------
 *
 * 2026-09-10 03:56 KST 대표님: 「가려져있어요」. 사진 한 장뿐이었고, 거기에는 `/admin/venues`
 * 구장 관리 표의 **[고치기] 칸이 통째로 오른쪽 밖으로 나가 있었다.**
 *
 * 원인은 바로 위 두 줄이다. `table.tbl` 의 모든 칸이 `white-space: nowrap` 이라 표의
 * 최소 폭이 **내용 길이로 정해진다.** 구장 주소가 "대구 달성군 다사읍 달구벌대로 943
 * 강창파크골프장 18홀" 처럼 길어서 이 표만 최소 1100px 을 요구했고, 상자(.tbl-scroll)가
 * 그보다 좁으면 `overflow-x: auto` 가 넘친 만큼을 **잘라서 숨긴다.** 데스크톱에는 그
 * 가로 스크롤바가 보이지도 않는다(요즘 브라우저는 겹침 스크롤바다). 대표님 눈에는
 * 그냥 **버튼이 사라진 화면**이다.
 *
 * 실측(v1.32.0, 운영 DB 사본, 구장 9개):
 *
 *     화면폭  상자폭  표폭   넘침   잘린 [고치기]
 *     1024    994     1100   106    9개 전부      ← 대표님이 보신 화면
 *     1152    1122    1122     0    없음
 *     1920    1530    1530     0    없음
 *
 * 1024 는 특별한 숫자가 아니다. 1920 모니터 175% 확대 = 1097, 1366 노트북 125% 확대 =
 * 1093 이다. 어르신이 글씨를 키워 쓰시면 바로 이 구간으로 들어온다. **"내 1920 에서는
 * 멀쩡하다" 로 끝내면 안 되는 고장이다.**
 *
 * 고침: 이 표만 **긴 글 칸의 줄바꿈을 허용**한다. 그러면 표의 최소 폭이 내용이 아니라
 * 칸 수로 정해져서 `width: 100%` 가 이기고, 표가 **언제나 상자 안에 들어온다.**
 * `word-break: keep-all` 은 한글을 낱말 가운데서 자르지 않는다(빈칸에서만 넘어간다).
 * `overflow-wrap: anywhere` 는 빈칸 없는 긴 토막 하나가 칸보다 길 때만 쓰이는 비상구다.
 *
 * **768px 미만(폰)은 건드리지 않는다.** 폰에서는 손가락으로 미는 가로 스크롤이 제대로
 * 동작하고, 좁은 칸에서 주소를 줄바꿈하면 한 줄이 네 줄이 되어 더 나빠진다. 그래서 이
 * 규칙은 통째로 `min-width: 768px` 안에 있다(390px 화면은 v1.32.0 과 한 픽셀도 다르지 않다).
 *
 * 칸에 붙이는 표시:
 *   (없음)  줄바꿈 허용. 이름 · 주소처럼 긴 글이 들어오는 칸.
 *   .nw     줄바꿈 금지. 시간 · 단위 · 상태처럼 짧고 잘리면 안 되는 칸.
 *   .act    버튼 칸. 내용만큼만 차지하고 남는 폭은 긴 글 칸에 넘긴다.
 *
 * --- v1.34.0: 제보를 기다리지 않고 **나머지 표를 전부 미리 쟀다** -----------
 *
 * v1.33.0 은 제보가 온 구장 표 한 장만 고쳤다. 그런데 이 고장의 원인(위 두 줄의 nowrap)은
 * **모든 `table.tbl` 이 공유한다.** 그래서 운영자 표 열두 장을 대표님의 실제 폭에서 다 쟀다
 * (`scripts/measure-admin-tables.js`, 증거 `evidence/v1340/`).
 *
 * 나온 답은 둘이고, 둘 다 중요하다.
 *
 * **1) 오늘 운영 데이터로는 한 장도 안 잘린다.** 1024 · 1097 · 1440 · 1920 네 폭 전부
 *    넘침 0 이다. "다른 표도 지금 잘려 있을 것" 이라는 짐작은 **틀렸다.**
 *
 * **2) 그런데 여유가 없다.** 표의 최소 폭은 글자 수로 정해지므로, 대표님이 조금만 긴 글을
 *    넣으시면 그날 잘린다. 이 클럽이 **이미 넣어 둔** 글자 수(구장 주소 26자 · 포스터 이름
 *    20자 · 포인트 메모 38자)를 다른 칸에도 넣어 보고 클론에서 다시 쟀다:
 *
 *      화면            최소 폭   1024px(994) 여유    판정
 *      경기 관리        1029      **-35**            잘린다 ([상태] 열)
 *      참가비 기록      1033      **-39**            잘린다 ([상태] 열)
 *      가맹점 관리      1023      **-29**            잘린다 ([고치기] 4개 전부)
 *      회원 관리         976      +18                한 칸 차이
 *      입금 확인         970      +24                한 칸 차이 ([입금 확인] 버튼)
 *      정산 상세 명세    813      +181               아래 (*) 참조
 *      ---------------------------------------------------------------
 *      가맹점 정산 요약  764      +230               넉넉하다. 안 건드린다.
 *      경기 상세 명단    668      +326               넉넉하다. 안 건드린다.
 *      후원사 관리       642      +352               넉넉하다. 안 건드린다.
 *      홍보 포스터       593      +401               넉넉하다. 안 건드린다.
 *      정합성 점검       560      +434               넉넉하다. 안 건드린다.
 *
 * 그래서 **위 여섯 장에만** `fitwide` 를 붙였다(구장 표는 v1.33.0 부터 달고 있다).
 * 아래 다섯 장은 **일부러 안 붙였다.** 여유가 200px 넘게 있고, 안 넘치는 표에 이 규칙을
 * 붙여도 아무 일도 안 일어나지만(줄바꿈은 내용이 폭을 넘을 때만 생긴다) **안 바뀌는 것을
 * 확인하는 비용이 매번 든다.** 필요해지면 그때 재고 붙일 것. 기준은 위 표의 "여유" 다.
 *
 * (*) 정산 상세 명세는 위 기준으로는 넉넉했지만, 회원 이름을 23자까지 늘린 **한계 시험**
 *     에서 1024px 에 43px 넘쳤다. 실제 회원 이름은 길어야 8자라 오늘 날 고장은 아니다.
 *     다만 이 표의 「메모」 가 "가맹점 포인트 사용 (<가맹점 이름>)" 이라 **대표님이 가맹점
 *     이름을 길게 지으시면 같이 길어진다.** 재 놓고 안 고치느니 붙였다.
 *
 * 다음 사람에게: 이 표에 열을 하나 더하거나 자유 글 칸을 늘리면 **최소 폭이 그만큼 는다.**
 * `node scripts/measure-admin-tables.js --readonly --widths 1024,1097` 로 다시 재라.
 * 1920 에서 재면 셋 다 통과한다. 그 폭에서는 원래 안 잘린다.
 */
@media (min-width: 768px) {
  table.tbl.fitwide th,
  table.tbl.fitwide td { white-space: normal; word-break: keep-all; overflow-wrap: anywhere; }
  table.tbl.fitwide th.nw,
  table.tbl.fitwide td.nw,
  table.tbl.fitwide th.n,
  table.tbl.fitwide td.n { white-space: nowrap; }
  table.tbl.fitwide th.act,
  table.tbl.fitwide td.act { white-space: nowrap; width: 1%; }
}

/* ------------------------------------------------------------------ 통계 타일
 *
 * **어디에 쓰고 어디에 쓰지 않는가 (v1.18.0에서 정한 선이다. 넓히지 말 것)**
 *
 * 쓰는 곳: 그 화면의 **주제 자체가 숫자인 화면**의 머리. `/admin/deposits` 의
 * 대기·안내·환불 건수, `/admin/settlement` 의 월 합계가 그렇다. 바로 아래 표가
 * 그 숫자를 풀어 놓은 것이라 요약이 먼저 오는 것이 맞다.
 *
 * 쓰지 않는 곳: **운영 현황(`/admin`) 같은 대시보드의 첫 화면.** v1.17.0 까지 그 화면은
 * 타일 13칸으로 시작했다(전체 회원·모집중·오늘·가맹점 + 종목별 6 + 참가비 3). 대표님이
 * 그 화면에서 실제로 하는 일은 숫자를 보는 것이 아니라 **다가오는 경기를 열어 보고
 * 관리 메뉴로 들어가는 것**인데, 그 두 가지가 타일 벽 아래에 있었다. 지금은 일이 먼저
 * 오고 숫자는 `.list` 줄로 내려갔다(같은 숫자, 같은 링크, 자리와 밀도만 바뀌었다).
 * 대시보드를 타일 격자로 되돌리지 말 것.
 */

.stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.stats.c3 { grid-template-columns: repeat(3, 1fr); }
.stat { background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: var(--s4); }
.stat .k { font-size: var(--fs-sm); color: var(--ink-2); font-weight: 700; }
.stat .v { font-size: var(--fs-2xl); font-weight: 800; font-variant-numeric: tabular-nums; margin-top: 3px; letter-spacing: -0.6px; }
.stat .v small { font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2); margin-left: 2px; }

/* 포인트 잔액 카드.
 * **v1.17.0 에서 그라데이션을 걷어냈다.** 예전에는 초록 두 단계가 대각선으로 섞였고,
 * 그 위에 흰 글씨가 얹혀 있어서 위치마다 대비가 달랐다(장식이 대비를 흔든 것이다).
 * 지금은 한 가지 색이라 잔액 숫자의 대비가 화면 어디서나 7.61:1 로 같다.
 * 그라데이션을 다시 넣지 말 것. */
.balance {
  background: var(--turf);
  color: #fff; border-radius: var(--r); padding: var(--s5);
}
.balance .k { font-size: var(--fs-base); color: #fff; font-weight: 700; }
.balance .v { font-size: 46px; font-weight: 800; letter-spacing: -1px; font-variant-numeric: tabular-nums; margin-top: 2px; }
.balance .v small { font-size: var(--fs-lg); margin-left: 3px; font-weight: 700; }
.balance .act { display: flex; gap: var(--s2); margin-top: var(--s4); }
/* 초록 위의 흰 반투명 버튼은 대비가 애매해진다. 테두리를 둘러 경계를 확실히 한다. */
.balance .act a {
  flex: 1; min-height: var(--ctl-h); border-radius: var(--r-sm);
  background: transparent; border: 2px solid #fff; color: #fff;
  display: flex; align-items: center; justify-content: center; gap: 7px; font-weight: 800; font-size: var(--fs-md);
}
.balance .act a.solid { background: #fff; border-color: #fff; color: var(--turf-dark); }

/* ------------------------------------------------------------------ QR */

.qr-stage { background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: var(--s5); text-align: center; }
.qr-stage img { width: 100%; max-width: 320px; aspect-ratio: 1; image-rendering: pixelated; border-radius: var(--r); }
.qr-stage .who { font-size: var(--fs-xl); font-weight: 800; margin-bottom: 4px; }
.qr-stage .sub { font-size: var(--fs-base); color: var(--ink-2); margin-bottom: 18px; }
.qr-stage .ttl-left { margin-top: var(--s4); font-size: var(--fs-base); color: var(--ink-2); font-weight: 700; }
.qr-stage .ttl-left b { color: var(--warn); font-variant-numeric: tabular-nums; }

#reader { border-radius: var(--r); overflow: hidden; background: #000; }
#reader video { width: 100% !important; height: auto !important; display: block; }

/* ------------------------------------------------------------------ 하단 탭 */

/* 하단 탭 다섯 칸. 이 앱의 유일한 이동 수단이고 **2단 메뉴는 없다.**
 * v1.17.0 에서 두 가지가 바뀌었다:
 *   1. 그림이 이모지에서 선 그림(SVG)으로 바뀌었다. 이모지는 기기마다 다른 그림이
 *      나오고 색을 정할 수 없어 대비를 보장할 수 없었다.
 *   2. 글씨가 13px → 15px. 이 앱에서 제일 작던 글씨였고 바닥(14px)을 밑돌았다.
 *      그래서 띠 높이도 70 → 72px 로 같이 올렸다. 다시 줄이지 말 것. */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  background: var(--card);
  border-top: 1px solid var(--line); box-shadow: var(--shadow);
  display: flex; height: calc(var(--tab-h) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
}
.tabbar a {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2);
  min-height: var(--tap); padding: 0 2px; text-align: center; line-height: 1.15;
}
.tabbar a .i { width: 24px; height: 24px; }
/* 지금 있는 칸은 글자색 + 위쪽 굵은 선 두 가지로 표시한다. 색 하나로만 알리면
   색을 구별하기 어려운 회원에게는 아무 표시도 없는 것과 같다. */
.tabbar a.on { color: var(--turf-dark); box-shadow: inset 0 3px 0 var(--turf-dark); }


/* =========================================================================
 * 지정 · 인증 서류 (`.certs`) — **두 화면이 같이 쓰는 부품** (v1.31.0 에 home.css 에서 옮김)
 * =========================================================================
 *
 * 2026-09-10 01:46 KST, 대표님이 그대로 적어 보내신 말: **「지정서도 안나오고요」**.
 * 바로 앞뒤 문장이 「일반회원들이 보는화면. 컴터로」 라, 보고 계신 화면은 **로그인한
 * 회원 앱**이었다.
 *
 * 서류가 깨진 것이 아니었다. 실측하면 넉 장 다 정확히 같은 높이로 멀쩡히 그려진다
 * (1440px 소개 홈페이지, evidence/v1310/measure-certs-before.txt):
 *
 *     지정서 396.98x558 · 사회적기업 467.98x558 · 설립인가증 377.3x558 · 사업자등록증 558x558
 *
 * 진짜 원인은 **놓인 자리**였다. 이 구역은 `views/pages/home.ejs`(소개 홈페이지) 한 곳에만
 * 들어가 있었는데, `src/routes/home.ts` 의 첫 줄이 `if (req.user) return next()` 다.
 * 즉 **로그인하는 순간 소개 홈페이지 자체가 닿을 수 없는 화면이 된다.** 대표님은 회원
 * 계정으로 들어가 보고 계셨으므로 서류를 보실 방법이 없었다. v1.31.0 에서 회원 홈
 * (`views/pages/matches.ejs`)에도 **같은 partial 을** 세웠고, 그러려면 이 CSS 가
 * 두 화면이 다 읽는 파일에 있어야 했다. 그래서 여기로 옮겼다.
 *
 * **부품을 두 벌로 만들지 말 것.** 회원 앱용 `.member-certs` 를 새로 만드는 순간
 * 한쪽만 고쳐지는 날이 온다(NOTES 8d · 8ab · 8ai 가 전부 그 이야기다).
 *
 * 아래 내용은 home.css 에서 **한 글자도 바꾸지 않고** 옮겨 온 것이다. 이어서
 * `.certs` 의 제목 · 구역 여백만 회원 앱 쪽 값으로 새로 붙인다(소개 홈페이지에서는
 * home.css 의 `.sect` 규칙이 나중에 읽혀서 그대로 이긴다).
 */

/* ------------------------------------------------------------------ 지정 · 인증 서류 (v1.19.0)
 *
 * 대표님이 /admin/certificates 에 올린 서류 사진들이다. 한 장도 없으면 구역이 통째로
 * 안 그려진다(partial 쪽에서 막는다). 규칙 셋:
 *
 *   1. **테두리는 사진 한 겹뿐.** 사진 상자를 다시 카드에 넣지 않는다(선이 두 겹으로
 *      겹치고, 좁은 폰에서 사진 넓이를 그만큼 잃는다).
 *   2. **이름이 사진 아래에 글자로** 붙는다. 그림만 늘어놓으면 무슨 서류인지 모르고,
 *      그 이름이 그대로 이미지 alt 다(사진이 안 떠도 읽힌다).
 *   3. 알약 모양·둥근 배지를 쓰지 않는다. 서류는 읽는 것이지 누르는 버튼이 아니다
 *      (누를 수 있다는 것은 아래 "크게 보기" 한 줄이 글자로 말한다).
 *
 * 몇 장씩 서는지는 아래 "넓은 화면" 칸에서 정한다. A4 세로라 한 칸이 세로로 길다.
 *
 * ---------------------------------------------------------------------------
 * v1.21.0: 여기 있던 `@media (min-width: 560px) { 두 칸 }` 을 지웠다
 * ---------------------------------------------------------------------------
 *
 * 그 한 줄이 **창을 키울수록 서류가 작아지는** 고장이었다. 두 칸으로 갈라지는 560px
 * 시점에 부모(.pub)는 아직 640px 로 묶여 있어서, 칸이 반으로 접히기만 하고 늘어날
 * 자리가 없었다. 실측:
 *
 *     559px 창 → 서류 525x742      560px 창 → 서류 252x356   (1px 차이로 반토막)
 *     640px 이상 → 292x413 에서 그대로 멈춤
 *     그런데 390px **폰**에서는 356x503 이었다.
 *
 * 즉 1920 모니터에서 본 서류가 폰에서 본 것보다 작았다. 게다가 서류가 3장이라 두 칸
 * 격자에서는 마지막 한 장이 늘 혼자 남았다.
 *
 * 지금은 **한 칸(1280px 미만) → 세 칸(1280px 이상)** 이다. 3장이 정확히 한 줄이라
 * 혼자 남는 장이 없고, 어느 폭에서도 서류가 폰에서보다 작아지지 않는다(실측은 NOTES 9a).
 * 두 칸 단계를 다시 넣지 말 것 — 3장이 2+1 로 갈라지고 1024px 대에서 폰보다 작아진다.
 *
 * ---------------------------------------------------------------------------
 * v1.23.0: 격자를 걷어내고 **후원사와 같은 회전 상자**로 바꿨다
 * ---------------------------------------------------------------------------
 *
 * 대표님 말 그대로: **"인증서는 지정서랑. 조그만하게 나와도 됩니다. 돌아가게끔."**
 * 크기를 지키라는 v1.21.0 의 제약을 **대표님이 직접 푸셨다.** 그래서 위 문단의 세 칸
 * 격자(1280px)는 사라졌고, 어느 폭에서나 **한 번에 한 장**이 서고 5초마다 넘어간다.
 * 격자를 되살리지 말 것 — 되살리는 순간 "돌아가게끔" 이 깨진다.
 *
 * 회전은 `.rotbox`(app.css) + `public/js/rotator.js` 다. 여기서는 칸의 생김새만 정한다.
 * `.cert-list` 는 이제 격자가 아니라 `.rot-track` 을 겸한 ul 이라 `display:flex` 는
 * `.rotbox .rot-track` 이 준다. **여기서 다시 grid 를 켜면 회전이 통째로 죽는다.**
 */
.certs .cert-list { list-style: none; margin: 0; padding: 0; }
/* 상자 위 첫 칸과 제목 사이 여백. 예전 격자의 margin-top 자리다. */
.certs .rot-view { margin-top: var(--s4); }
/* 칸 높이는 제일 긴 서류가 정한다. 짧은 서류는 위에 붙는다(가운데로 띄우면 아래
   [이전]/[다음] 줄과의 간격이 칸마다 달라져서 화면이 들썩인다). */
.certs .rot-slide { display: flex; align-items: flex-start; }
.certs .rot-slide > .cert { width: 100%; }
.certs .cert { display: block; text-decoration: none; color: var(--ink); }
/* v1.30.0: 높이를 맞추는 것은 **칸이 아니라 서류 그림**이다.
 *
 * 대표님 말씀 그대로: **"지정서 크기가 다르다", "같은 사이즈로 만들어주세요"**,
 * 그리고 v1.29.0 을 배포한 뒤에 다시 **"아직 안바껴있네요 ㅠㅠ"**.
 *
 * ### v1.29.0 이 왜 대표님 화면에서 안 바뀐 것으로 보였나 (이 절의 요점)
 *
 * v1.29.0 은 `.pic` 을 `aspect-ratio: 3/5` 고정 상자로 만들고 그림을 그 안에 contain
 * 으로 넣었다. **상자 높이는 완벽하게 같아졌다.** 그런데 상자는 배경도 테두리도 거의
 * 안 보이므로 대표님 눈에 들어오는 것은 상자가 아니라 **서류 그림**이고, contain 은
 * 폭을 꽉 채운 다음 남는 높이를 **빈 자리로 버린다.** 그래서 그려진 서류는 그대로
 * 제각각이었다 (운영 실측, 390px, evidence/v1300/measure-certs-before-prod.txt):
 *
 *     지정서 356x500.39  사회적기업 356x424.48  설립인가증 356x526.5  사업자등록증 356x356
 *     → 그려진 높이 356~526.5 (170.5px 차이). 사업자등록증 아래에 빈 자리가 239px.
 *
 * 우리 실측 도구도 "높이 종류 1가지, 가지런하다" 로 통과시켰다. **칸을 재고 있었기
 * 때문이다.** 대표님은 그림을 보신다(도구는 이번에 그려진 높이도 세도록 고쳤다).
 *
 * ### 고침 — 홍보 포스터(v1.28.0)와 **같은 약속**으로 맞춘다
 *
 * `.pic-tile`(app.css)이 포스터·공지 첨부에서 이미 하고 있는 것과 같다:
 * **그려진 높이를 하나로 모으고, 폭은 서류마다 제 비율에서 따라 나온다.** 자르지 않는다.
 * 다른 것은 높이를 어디서 얻느냐 하나뿐이다. 포스터는 한 판에 여러 장이 서므로 px 로
 * 못박고(`--nh`), 서류는 한 판에 한 장이라 **칸 폭에서 받아 온다.** 그래서 부품을
 * 합치지 않았다. 안쪽 약속은 같으니 한쪽만 고치지 말 것.
 *
 *   상자   aspect-ratio: var(--cert-ar)   ← 서버가 넘기는 **제일 넓은 서류의 비율**
 *   그림   height: 100%; width: auto      ← 칸 높이를 그대로 다 쓴다. 버리는 높이가 없다.
 *
 * 왜 하필 "제일 넓은 서류의 비율" 인가: 상자 높이 = 상자 폭 ÷ 그 비율이므로 어떤
 * 서류든 `높이 x 제 비율 <= 상자 폭` 이 **산술적으로 보장된다.** 그래서 `max-width:
 * 100%` 가 폭을 깎는 일이 없다(깎이면 그 한 장만 낮아져서 다시 들쭉날쭉해진다).
 * 대표님이 나중에 가로로 긴 표창장을 올리셔도 자동으로 맞는 이유가 이것이다.
 * **여기에 3/5 같은 고정 숫자를 도로 적지 말 것** — 그 순간 제일 넓은 서류가 폭에
 * 걸려 혼자 낮아지고, 이 절이 고친 고장이 그대로 되살아난다.
 *
 * 대표님이 v1.23.0 에 **"조그만하게 나와도 됩니다"** 라고 하셨으므로, 높이를 맞추느라
 * 제일 긴 서류가 526 → 356 으로 줄어드는 것은 허용된 거래다. 원본은 눌러서 새 창에서 본다.
 *
 * 절대 지키는 것: **자르지 않는다.** cover 로 바꾸지 말 것. 관인·제목·발급번호가
 * 서류의 위아래 끝에 있어서 정확히 그 자리가 먼저 잘려 나간다.
 */
.certs .cert .pic {
  display: flex; align-items: center; justify-content: center;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-sm);
  overflow: hidden;
  /* 서버가 서류 크기를 못 읽었을 때의 낙하산은 정사각이다. A4 세로 스캔(0.6~0.85)은
     전부 이보다 좁으므로 그 경우에도 어느 서류가 폭에 걸리지 않는다. */
  aspect-ratio: var(--cert-ar, 1 / 1);
  /* 서류가 안 떠도(네트워크가 느려도) 칸이 제 크기로 서서 화면이 들썩이지 않게 한다.
     가로·세로 48px 바닥은 이 클럽의 누름 자리 규칙이다(tokens.css 의 --tap). */
  min-height: var(--tap); min-width: var(--tap);
}
/* **칸 높이를 그대로 다 쓰는 그림.** 폭은 `--ar`(서버가 넘기는 그 서류 한 장의 비율)에서
   따라 나온다. 그림이 아직 안 내려왔어도 `--ar` 덕분에 폭이 0 으로 무너지지 않는다
   (v1.26.0 에 누름 자리가 336x35 로 무너진 그 사고). `--ar` 을 지우지 말 것. */
.certs .cert .pic img {
  display: block; margin: auto;
  height: 100%; width: auto;
  aspect-ratio: var(--ar, 3 / 4);
  max-width: 100%;
  object-fit: contain;
}
.certs .cert .cap {
  display: block; margin-top: var(--s3);
  font-size: var(--fs-md); font-weight: 800; line-height: 1.45; letter-spacing: -0.3px;
}
/* --ink-3 는 흰 바탕 6.08:1 이다. 더 흐린 회색으로 낮추지 말 것(tokens.css 참조). */
.certs .cert .more {
  display: block; margin-top: var(--s1);
  font-size: var(--fs-base); line-height: 1.5; color: var(--ink-3);
  text-decoration: underline; text-underline-offset: 3px;
}
.certs .cert:active .pic { background: var(--card-2); border-color: var(--ink-3); }

/* ===========================================================================
 * ★v1.46.0: 서류를 **후원사 줄과 같은 크기·같은 모양**으로 맞춘다★
 * ===========================================================================
 *
 * 대표님이 그대로 적어 보내신 말: **「인정서는 제하단에 넣어주시고 후원사 사이즈로 통일 부탁」**
 *
 * 그래서 두 가지를 했다. 자리는 EJS 에서 맨 아래로 내렸고(`pages/matches.ejs` ·
 * `pages/home.ejs`), 크기는 여기서 **후원사 카드(`.sponsor`)의 값을 그대로** 가져온다:
 * 96x96 로고 칸 · 13px 사이 · `var(--r)` 모서리 · 같은 테두리색. 후원사 쪽 값을 고치면
 * 여기도 같이 고칠 것 - 두 구역이 같은 크기여야 한다는 것이 대표님 요청이다.
 *
 * ### 잘리지 않는다. 대신 옆에 흰 자리가 남는다 (letterbox)
 *
 * 서류는 세로로 긴 A4 스캔(비율 0.68~0.84)이고 후원사 칸은 정사각(1.0)이다. 96px 칸에
 * 맞추면 **세로가 먼저 차고 좌우에 빈 자리가 남는다**(예: 지정서 895x1258 → 68x96).
 * 그 빈 자리를 없애려면 서류를 잘라야 하는데, 관인·제목·발급번호가 서류의 위아래 끝에
 * 있어서 정확히 그 자리가 먼저 사라진다. **`cover` 로 바꾸지 말 것.**
 * 후원사 로고도 같은 방식이라(`.sponsor .logo img` 가 contain 이다) 두 구역이 같은
 * 규칙으로 서고, 실제로 후원사 로고들도 44~71px 로 폭이 제각각이다.
 *
 * ### 「칸만 맞추고 고쳤다고 하지 말 것」
 *
 * v1.29.0 에서 칸 높이만 맞춰 놓고 보고했다가 대표님께 「그대로인데요」를 들었다.
 * 이 판은 `scripts/measure-v1460.js` 가 **그려진 그림**(getBoundingClientRect)을 세 장
 * 모두 재서 높이가 한 값인지, 그 값이 후원사 로고의 높이와 같은지까지 확인한다.
 *
 * ### 회전과 칸 수는 그대로다
 * `.rotbox.multi` 그대로라 768px 두 장 · 1024px 이상 세 장이고 5초마다 넘어간다.
 * 「인증서도 3개씩 좀될까요」(v1.35.0) · 「돌아가게끔」(v1.23.0)은 취소된 적이 없다. */
.certs .cert {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  grid-template-areas: "pic cap" "pic more";
  align-items: center; align-content: center;
  column-gap: 13px; row-gap: 2px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  padding: 12px 14px; min-height: 116px;
}
.certs .cert .pic {
  grid-area: pic;
  /* 후원사 로고 칸과 **같은 값**이다(.sponsor .logo). 위쪽의 `--cert-ar` 비율 칸을
     여기서 정사각 고정으로 덮는다. `--cert-ar` 자체는 지우지 않는다 - 서버가 넘기는
     값이고, 이 규칙을 되돌릴 때 다시 필요하다. */
  width: 96px; height: 96px; aspect-ratio: 1 / 1;
  min-width: 0; min-height: 0;
}
.certs .cert .pic img {
  /* `height: 100%` 를 쓰지 않는다. 정사각 칸에서 100% 는 세로로 긴 스캔을 **가로로
     칸 밖까지** 밀어내고, `overflow:hidden` 이 그만큼 잘라 낸다(v1.21.0 후원사 로고에서
     똑같이 겪은 함정이다). max-*/auto 조합이라 어느 비율이든 안 잘린다. */
  height: auto; width: auto; max-width: 100%; max-height: 100%;
  aspect-ratio: auto; object-fit: contain;
}
/* ------------------------------------------------- 서류 칸이 커진다 (v1.60.0)
 * 위 v1.46.0 이 정한 96x96 은 **어느 폭에서나 96** 이었다. 그래서 1920 모니터에서도
 * 서류 그림이 폰과 똑같은 크기로 서고, 옆 글자칸만 길어졌다. 실측(그려진 <img> 크기,
 * 패치 전): 768 · 1097 · 1920 세 폭 모두 `67x94 · 79x94 · 64x94 · 94x94` 로 **얼어
 * 있었다**. v1.29.0 의 「칸만 맞추고 고쳤다고 하지 말 것」 그대로, 아래 값은 칸이 아니라
 * **그려진 그림**을 다시 재서 확인했다:
 *   390  : 67x94 · 79x94 · 64x94 · 94x94  (변화 없음. 문서 높이 10044 그대로)
 *   768  : 84x118 · 99x118 · 80x118 · 118x118   (칸 327x146)
 *   1097 : 84x118 · 99x118 · 80x118 · 118x118   (칸 309x146, 글자칸 146)
 *   1920 : 141x198 · 166x198 · 134x198 · 198x198 (칸 517x226, 글자칸 274)
 *
 * ★1024px 에 단계를 하나 더 두지 않은 것은 일부러다★ — 대표님 화면이 바로 그 폭
 * (1920 모니터 175% = 실효 1097px)이고, 거기서 그림을 더 키우면 옆 글자칸이 134px 까지
 * 눌린다(직접 재 봤다). 넓은 화면은 1600px 단계가 받는다.
 *
 * ★백분율(%)로 쓰지 말 것★ — `clamp(96px, 40%, 200px)` 을 먼저 시도했다가 **1097 에서
 * 그림이 768 보다 작아졌다**(768 84x118 → 1097 70x98). 기준이 되는 칸 자체가
 * `.rotbox.multi` 의 `--per` 2→3 때문에 1024 에서 좁아지기 때문이다. 고정 px 로 둔다.
 *
 * `--doc-pic` 한 값이 서류 칸과 후원사 로고 칸을 **같이** 움직인다(아래 `.sponsors`
 * 쪽 참조). 「후원사 사이즈로 통일」이 커진 뒤에도 유지되는 이유다. */
@media (min-width: 768px)  { :root { --doc-pic: 120px; } }
@media (min-width: 1600px) { :root { --doc-pic: 200px; } }
@media (min-width: 768px) {
  .certs .cert { grid-template-columns: var(--doc-pic, 96px) minmax(0, 1fr); }
  .certs .cert .pic { width: var(--doc-pic, 96px); height: var(--doc-pic, 96px); }
}

.certs .cert .cap {
  grid-area: cap; align-self: end; margin-top: 0;
  font-size: var(--fs-control); line-height: 1.3;
}
.certs .cert .more { grid-area: more; align-self: start; margin-top: 0; font-size: var(--fs-base); }

/* ---------------------------------------------------------------------------
 * v1.35.0 — 대표님: **「인증서도 3개씩 좀될까요」**
 * ---------------------------------------------------------------------------
 *
 * 한 판에 몇 장이 서는지는 이 파일이 정하지 않는다. `views/partials/certificates.ejs` 가
 * 구역에 **`multi`** 를 붙였고, 그러면 위쪽 `.rotbox.multi` 규칙(768px 두 칸 · 1024px 이상
 * 세 칸)이 그대로 걸린다. 협력 기관 띠와 **같은 클래스, 같은 폭 단계**다. `.certs` 전용
 * `--per` 를 여기 새로 적지 말 것 - 적는 순간 v1.32.0 의 「협력기관도 지정서랑 같은
 * 사이즈로」 가 깨지고, 두 구역이 서로 다른 날 서로 다르게 고쳐진다.
 *
 * ### 여기서 진짜로 고친 것은 **폭 상한 한 줄**이다 (이걸 빼면 요청이 무효가 된다)
 *
 * v1.23.0 은 `rot-view` 와 `rot-nav` 를 **함께** 560px 로 묶었다. 한 판에 한 장일 때는
 * 그게 맞았다(한 장이 558x558, 버튼도 알맞은 크기). 그런데 그 상한을 그대로 둔 채
 * 칸만 셋으로 나누면 한 칸이 **186px** 이 되고, 그리면 서류가 186 높이로 그려진다 -
 * **폰(390px)에서 보던 360 보다 작다.** 창을 키울수록 서류가 작아지는 v1.21.0 의 그
 * 고장이 그대로 돌아온다. 그래서 v1.32.0 이 협력 기관 띠에서 한 것과 **같은 조치**를 한다:
 *
 *   `rot-view`  상한을 푼다 → 칸이 본문 폭을 셋으로 나눠 가진다 (서류가 커진다)
 *   `rot-nav`   560px 에 그대로 묶어 둔다 → [이전]/[다음] 하나가 500px 짜리 띠가 되지 않는다
 *               (v1.22.0 에 협력 기관 띠에서 겪은 그 고장. 이 상한은 여전히 유효하다)
 *
 * 폭이 갈라졌으므로 두 줄로 적는다. 다시 한 줄로 합치지 말 것.
 *
 * 실측(운영, 회원 홈, 로그인 상태. evidence/v1350/): 1097px 에서 한 장 558 -> 세 장이
 * 나란히 서고 그려진 높이가 셋 다 같다. 숫자는 NOTES 8an 의 표에 있다.
 *
 * 예전 주석에 있던 "서류는 글자를 읽는 그림이라 여전히 한 판에 한 장이고 폭을 묶는다" 는
 * 대표님 요청으로 **취소되었다.** 원본을 크게 보는 길은 그대로 남아 있다(눌러서 새 창). */
@media (min-width: 768px) {
  .certs .rot-view { max-width: none; margin-left: auto; margin-right: auto; }
  .certs .rot-nav { max-width: 560px; margin-left: auto; margin-right: auto; }
  /* v1.27.1 까지 여기 `.posters .poster img { max-height: 760px }` 이 있었다. 포스터
     높이를 이제 `.pic-tile` 의 `--nh`(150/180) 가 정하므로 상한이 할 일이 없다.
     되살리지 말 것 - 높이가 두 곳에서 정해지면 어느 쪽이 이겼는지 화면을 봐야 안다. */
}

/* 회원 앱에서의 구역 머리. 소개 홈페이지는 `.sect > h2`(home.css, 48px)가 받고 이 규칙을
   덮는다 — 특이도가 `(0,2,0)` 로 완전히 같아서 **나중에 읽히는 home.css 가 이긴다.**
   그래서 소개 홈페이지는 v1.30.0 과 한 픽셀도 다르지 않다(실측으로 확인했다).
   여기 값은 회원 앱의 다른 구역 머리(`.sect-head h2`)와 같은 규칙이다: 왼쪽 굵은 선 한 줄. */
.certs { margin-top: 26px; }
.certs > h2 {
  font-size: var(--fs-lg); font-weight: 800; line-height: var(--lh-tight);
  border-left: 4px solid var(--ink); padding-left: 10px;
}
.certs > .say { margin: var(--s3) 0 0; font-size: var(--fs-sm); line-height: 1.6; color: var(--ink-2); }


/* ------------------------------------------------------------------ 페이지네이션 */

.pager { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 16px; }
.pager a, .pager span {
  min-width: var(--ctl-h); min-height: var(--ctl-h); display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm); border: 1.5px solid var(--line); background: var(--card); font-size: var(--fs-md); font-weight: 800;
}
.pager a .i { width: 20px; height: 20px; }
/* v1.65.0: 넘기기 화살표도 눌린 표시가 없었다. 링크(`a`)만 - 아래 `span.cur`/`span.mute`
   는 누르는 것이 아니다. */
.pager a:active { background: var(--card-2); }
.pager span.cur { background: var(--ink); color: #fff; border-color: var(--ink); }
.pager span.mute { border: 0; background: transparent; color: var(--ink-2); font-weight: 700; font-size: var(--fs-sm); }

/* ------------------------------------------------------------------ 기타 */

.divider { height: 1px; background: var(--line); margin: var(--s4) 0; }
.kv { display: flex; gap: var(--s3); padding: var(--s3) 0; border-bottom: 1px solid var(--line-2); font-size: var(--fs-base); }
.kv:last-child { border-bottom: 0; }
.kv .k { color: var(--ink-2); width: 96px; flex: 0 0 auto; font-size: var(--fs-base); font-weight: 700; }
.kv .v { flex: 1; font-weight: 600; }
/* --ink-3 는 흰 바탕에서 6.08:1 이다. "흐린 글씨" 가 아니라 **한 단계 조용한 글씨**다. */
.muted { color: var(--ink-3); }
.small { font-size: var(--fs-sm); }
.center { text-align: center; }
.mono { font-family: var(--mono); font-size: var(--fs-xs); }
.nowrap { white-space: nowrap; }
.wrapline { white-space: pre-wrap; word-break: break-word; }

.auth-wrap { max-width: 440px; margin: 0 auto; padding: 30px 18px 60px; }
.auth-logo { text-align: center; margin-bottom: var(--s6); }
.auth-logo .mark { width: 72px; height: 72px; border-radius: var(--r-lg); background: var(--bar); color: #fff; display: grid; place-items: center; margin: 0 auto var(--s4); }
.auth-logo .mark .i { width: 38px; height: 38px; }
.auth-logo h1 { font-size: var(--fs-2xl); font-weight: 800; }
.auth-logo p { color: var(--ink-2); font-size: var(--fs-base); margin: 7px 0 0; }

/* 데스크톱. **v1.31.0 부터 여기가 회원 앱의 데스크톱 판을 정하는 자리다.**
 *
 * **v1.18.0: 하단 탭과 [신청하기] 띠의 둥근 위 모서리를 없앴다.** 넓은 화면에서 이 둘이
 * 12px 로 둥글려진 채 떠 있어서 "떠다니는 껍데기" 로 읽혔다(no-slop-ui 가 사이드바·툴바에
 * 대해 금지하는 그것이다). 이동 수단은 화면에 붙어 있어야지 떠 있으면 안 된다.
 * 각지게 두고 위 선 한 줄로만 본문과 가른다.
 *
 * ---------------------------------------------------------------------------
 * v1.31.0: **넓힌 폭을 "쓰는" 것까지가 이 판이다.**
 * ---------------------------------------------------------------------------
 *
 * `.wrap` 상한만 올리면 고장이 모양만 바뀐다: 카드가 한 줄에 하나씩 쌓인 채 폭만
 * 1560px 이 되어, 종목 칸이 944x147 짜리 띠가 됐던 것과 **똑같은 일**이 경기 카드 ·
 * 장터 카드에서 되풀이된다. 그래서 폭을 올리는 규칙과 여러 단으로 세우는 규칙이
 * 반드시 같이 있어야 한다. 한쪽만 되돌리지 말 것.
 *
 * 반대로 **글을 쓰는 칸은 넓히지 않는다.** 1560px 짜리 입력 한 줄은 어르신이 왼쪽 끝을
 * 보며 오른쪽 끝을 채워야 하는 칸이고, 이 앱의 기준 사용자에게는 640px 때보다 나쁘다.
 * v1.21.0 이 소개 홈페이지에서 `.hero-action` 을 460px 로 묶은 것과 같은 이유다. */
@media (min-width: 768px) {
  body { font-size: var(--fs-base); }
  .wrap { padding-bottom: 60px; }
  .tabbar { max-width: var(--app-max); margin: 0 auto; border-radius: 0; border: 0; border-top: 1px solid var(--line); }
  .sticky-cta { max-width: var(--app-max); margin: 0 auto; border-radius: 0; border: 0; border-top: 1px solid var(--line); }

  /* 머리띠 · 부제 띠도 본문과 같은 세로줄에 선다(바탕은 화면 끝까지, 종목 띠와 같은 방법). */
  .appbar {
    padding-left: max(var(--s4), calc((100% - var(--app-max)) / 2));
    padding-right: max(var(--s4), calc((100% - var(--app-max)) / 2));
  }
  .subbar {
    padding-left: max(12px, calc((100% - var(--app-max)) / 2));
    padding-right: max(12px, calc((100% - var(--app-max)) / 2));
  }

  /* 카드 목록이 여러 단으로 선다. `.wrap >` 로 묶는 이유: `.stack` 은 카드 **안쪽**
     에서도 쓰는 일반 클래스라 전부 격자로 만들면 폼 한 줄이 두 단으로 갈라진다.
     본문 바로 밑의 목록만 대상이다. */
  .wrap > .stack, .mk-list { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }

  /* 글 쓰는 칸과 주 동작은 넓히지 않는다(위 주석). 560 은 640px 본문에서 쓰던 폭과
     사실상 같아서 폰에서 익숙해진 모양이 그대로 남는다. */
  .wrap .f input, .wrap .f select, .wrap .f textarea { max-width: 560px; }
  .wrap .hero-action { max-width: 460px; margin-left: auto; margin-right: auto; }
}

@media (min-width: 1280px) {
  /* 1280 이상에서 세 단. 1560px 본문에서 카드 한 장이 약 500px 이라 경기 카드의
     일시 · 장소 · 참가비 세 줄이 줄바꿈 없이 들어간다(실측). 네 단으로 올리지 말 것 —
     그 아래에서 "일시" 줄이 접히기 시작한다. */
  .wrap > .stack, .mk-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* 공지의 1280 세 칸 규칙도 이 폭에서 같이 선다. 단, 그 규칙은 위쪽(app.css 2600 근처)
     "공지도 가로로" 사다리 안에 두었다. 같은 점수(specificity) 규칙은 파일에서 뒤에 온
     것이 이기므로, 이 블록에 `.notice-list` 를 또 적으면 540 두 칸 규칙이 사라지고
     1280 에서 두 칸으로 무너진다. v1.64.0 실측 고장이니 이 블록에 다시 적지 말 것. */
}

@media print {
  .appbar, .subbar, .sportpick, .sportnow, .tabbar, .sticky-cta, .btn { display: none !important; }
  body { background: #fff; }
  .wrap { padding: 0; max-width: none; }
}

/* ------------------------------------------------------------------ 고령 회원 배려 전용
 *
 * 한 화면에 "지금 눌러야 할 것" 하나를 크게 세운다. 아래 클래스들은 그 하나를 위한 것이고,
 * 보조 동작은 반드시 그보다 작고 아래에 둔다. 두 개를 나란히 크게 두면 고르는 데 시간이 걸린다.
 */

/* 한 화면에 하나뿐인 주 동작. **떠 있는 그림자를 뺐다** — 이 버튼은 이미 화면에서
   제일 크고 제일 진한 물건이라 그림자로 더 띄울 이유가 없다(장식은 대비만 흔든다). */
.hero-action {
  display: flex; align-items: center; justify-content: center; gap: var(--s2);
  width: 100%; min-height: var(--hero-h); border-radius: var(--r); border: 0;
  background: var(--turf); color: #fff; font-family: inherit; font-size: 23px; font-weight: 800;
  cursor: pointer; letter-spacing: -0.5px; padding: var(--s3) var(--s4); text-align: center;
}
/* 눌린 표시는 색만 (위 .btn:active 의 이유와 같다). 흰 글씨 9.00:1. */
.hero-action:active { background: var(--turf-dark); }
.hero-action.warn:active { background: var(--warn-bg); }
.hero-action:disabled { background: var(--ink-2); }
.hero-action.warn { background: var(--card); color: var(--warn); border: 2px solid var(--warn); }
.hero-action .i { width: 26px; height: 26px; }

/* 보조 동작 줄: 주 버튼 아래에 글자 링크로만 둔다 */
.sub-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 14px; }
.sub-actions a, .sub-actions button {
  min-height: var(--tap); padding: 0 var(--s4); display: inline-flex; align-items: center;
  font-size: var(--fs-base); font-weight: 700; color: var(--ink-2); background: none; border: 0;
  font-family: inherit; cursor: pointer; text-decoration: underline; text-underline-offset: 4px;
}

/* 큰 확인 표시 (가입 완료 · 결제 완료). 예전에는 52~56px 짜리 이모지였다.
   그림 하나만 두지 않는다 — 바로 아래 "가입이 끝났습니다" 같은 문장이 늘 붙어 있다. */
.welcome-mark {
  width: 64px; height: 64px; margin: 0 auto var(--s3);
  border-radius: 50%; background: var(--turf-bg); border: 2px solid var(--turf);
  color: var(--turf-dark); display: grid; place-items: center;
}
.welcome-mark .i { width: 36px; height: 36px; }

/* 순서가 있는 안내 (가입 · 사용법). 숫자를 크게 붙여 읽는 순서를 강제한다. */
.steps { display: grid; gap: 12px; }
.steps .step { display: flex; gap: 13px; align-items: flex-start; }
.steps .step .no {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: var(--r-sm);
  background: var(--bar); color: #fff; display: grid; place-items: center;
  font-weight: 800; font-size: var(--fs-md);
}
.steps .step .tx { font-size: var(--fs-base); line-height: 1.55; padding-top: 6px; }
.steps .step .tx b { font-weight: 800; }

/* 큰 안내문 (빈 화면, 확인 문구) */
/* 인원 증감기 (팀 예약).
   숫자 칸은 readonly 라 폰 키보드가 뜨지 않는다. 어르신이 작은 숫자칸을 눌러 지우고
   다시 치는 일이 없어야 해서 [−] [+] 를 크게 뒀다(각 64px, 최소 탭 크기의 넉넉히 위).
   이 크기를 줄이지 말 것. 장갑 낀 손과 떨리는 손가락이 기준이다. */
.stepper { display: flex; align-items: stretch; gap: 10px; }
.stepper .step {
  flex: 0 0 auto; width: 64px; height: 64px; border-radius: var(--r);
  border: 2px solid var(--line); background: var(--card-2); color: var(--ink);
  font-size: 30px; font-weight: 800; line-height: 1; cursor: pointer;
}
/* 눌린 표시는 색만 (위 .btn:active 와 같은 이유). --ink 글자가 이 바탕에서 15.7:1 이다. */
.stepper .step:active { background: var(--line-2); border-color: var(--ink-3); }
.stepper .step:disabled { color: var(--ink-3); border-color: var(--line-2); background: var(--card); }
.stepper .stepnum {
  flex: 1 1 auto; min-width: 0; height: 64px; text-align: center;
  font-size: var(--fs-2xl); font-weight: 800; border: 2px solid var(--line);
  border-radius: var(--r); background: var(--card); color: var(--ink);
}
/* 숫자 입력의 증감 화살표는 너무 작아서 오히려 방해가 된다. 감춘다. */
.stepper .stepnum::-webkit-outer-spin-button,
.stepper .stepnum::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.stepper .stepnum { -moz-appearance: textfield; }

/* 명단 한 줄이 팀일 때. 이름 옆에 인원을 붙여 "한 줄 = 여러 명" 임을 바로 읽게 한다. */
.badge.team { background: var(--sp-screenpark-bg); color: var(--sp-screenpark); border: 1px solid var(--sp-screenpark); }

/* ------------------------------------------------------------------ 종목별 표시 (v1.5.0)
 *
 * 고객이 종목마다 본뜰 서비스를 지목했다(풋살·축구=매치업, 야구=매치매치,
 * 스크린파크골프=골프존, 필드파크골프=레저로). 그 서비스들의 **정보 밀도는 가져오지
 * 않았다.** 어르신 화면에서 읽히는 것만 남겼다. 자세한 판단 근거는 NOTES 8g.
 */

/* 포지션 칩. 경기 상세의 [모집 포지션] 과 명단 이름 옆에 같은 모양으로 쓴다.
   두 곳에서 모양이 다르면 "저 회색 글자가 아까 그 포지션인가" 를 다시 생각하게 된다.
   글씨를 더 줄이지 말 것(14px 는 이 화면의 최소값이다). */
.badge.pos { background: var(--card-2); color: var(--ink); border: 1px solid var(--ink-3); }

/* 스크린파크골프 티타임 블록 (골프존 티타임 목록의 그 모양).
   매장 타석을 **시간으로 잡는 것**이라 회원이 제일 먼저 확인하는 값이 "몇 시" 다.
   그래서 시각만 본문에서 떼어 내 크게 세운다. 필드파크골프에는 붙이지 않는다
   (야외 코스는 시각보다 날씨와 코스가 먼저다). */
.teetime {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  margin: 0 0 var(--s3); padding: 13px 15px; border-radius: var(--r);
  background: var(--sp-screenpark-bg); border: 1px solid var(--sp-screenpark);
}
.teetime .t { font-size: var(--fs-3xl); font-weight: 800; color: var(--sp-screenpark); font-variant-numeric: tabular-nums; line-height: 1.1; }
.teetime .to { font-size: var(--fs-control); font-weight: 700; color: var(--sp-screenpark); font-variant-numeric: tabular-nums; }
.teetime .d { margin-left: auto; font-size: var(--fs-base); font-weight: 700; color: var(--ink-2); }

/* 개설 화면의 종목별 칸 묶음. 고른 종목의 것만 보인다(match-new.ejs 아래 스크립트). */
.sport-fields .pickers.pos-pick { grid-template-columns: 1fr 1fr; }

.notice-big { font-size: var(--fs-lg); font-weight: 700; line-height: 1.55; text-align: center; padding: 8px 0 4px; }
.notice-big .em { color: var(--turf-dark); font-weight: 800; }
.notice-big .bad { color: var(--warn); font-weight: 800; }

/* ------------------------------------------------------------------ 돌아가는 상자
 *                                                       (후원사 · 가맹점, v1.15.0)
 *
 * 클럽이 후원을 받고, 가맹점도 회원에게 보여 주어야 한다. 둘 다 보여 주되 **회원이
 * 지금 해야 할 일(경기 고르기, 신청하기)을 절대 밀어내지 않는다.** 지켜야 하는 규칙:
 *
 *   1. 위치는 항상 본문 **아래**다. 홈에서는 경기 목록과 [새 경기 열기] 뒤,
 *      경기 상세에서는 명단 뒤(하단 고정 [신청하기] 와 절대 나란히 두지 않는다).
 *   2. 글씨는 본문과 같은 크기(이름 19px / 한 줄 소개 17px), 대비는 본문과 같게.
 *      "광고니까 작게" 가 성립하지 않는 화면이다.
 *   3. 보여 줄 것이 없으면 상자 자체가 안 그려진다(partial 이 통째로 빠진다).
 *
 * v1.15.0 에서 **세로로 쌓기 → 한 곳씩 돌아가기**로 바꿨다. 고객 말 그대로
 * "후원사는 많으면 좋으니 돌아가게끔", "가맹점은 노출되어 후원사와 마찬가지로 돌라갔으면".
 *
 * v1.2.0 주석에는 "캐러셀을 만들지 않는다(다 읽기 전에 사라진다)" 고 적혀 있었다.
 * 그 걱정은 그대로 유효해서, 돌리되 **그 걱정을 없애는 방식으로** 만들었다:
 *   - 한 칸을 5~6초 세워 둔다(로고와 이름 한 줄을 읽기에 충분하다. 2초짜리 배너가 아니다).
 *   - 손가락이 닿으면 15초 멈춘다. 읽는 중에 화면이 도망가지 않는다.
 *   - [이전]/[다음] 글자 버튼이 항상 있다. 놓쳐도 되돌릴 수 있다.
 *   - 겹쳐서 페이드하지 않는다. 넘어가는 순간에도 "누구" 인지 흐려지지 않는다.
 * 이 넷 중 하나라도 빼면 v1.2.0 이 걱정하던 그 배너가 된다. 빼지 말 것.
 */

.rotbox { margin-top: 26px; }
.rotbox > .cap {
  display: flex; align-items: center; gap: var(--s2); margin: 0 2px var(--s3); flex-wrap: wrap;
}
.rotbox > .cap h2 { font-size: var(--fs-lg); font-weight: 800; border-left: 4px solid var(--ink); padding-left: 10px; }
.rotbox > .cap .sub { font-size: var(--fs-sm); color: var(--ink-2); font-weight: 700; }

/* ---------------------------------------------------------- 제목 크기 (v1.60.0)
 * 소개 홈페이지에서 회전 상자의 제목만 **어느 폭에서나 20px 로 굳어** 있었다. 바로 위
 * 아래 구역의 `.sect > h2`(home.css)는 768px 30px · 1024px 이상 48px 로 커지는데,
 * 같은 높이에 나란히 선 「후원사」·「협력 기관」 제목만 작아서 한 화면에 두 종류의
 * 제목 크기가 섞였다. 실측(패치 전 → 후): 390 20 → 20 · 768 20 → 30 · 1097 20 → 48 ·
 * 1920 20 → 48. 왼쪽 세로줄도 `.sect > h2` 와 같은 5px + `--s3` 로 맞춘다.
 * 소개 홈페이지(`.pub`)에서 `.certs > h2` 는 건드리지 않는다 — 인증서 구역은 `.sect`
 * 이기도 해서 home.css 의 `.sect > h2` 가 이미 이기고 있고(같은 명시도 0,2,0 에 나중
 * 순서), 거기서 또 적으면 두 벌이 된다.
 * 회원 앱(`.wrap`)은 1024px 이상에서 30px 까지만 올린다. 회원 화면은 본문 폭이
 * `--app-max` 로 묶여 있어 48px 이 줄을 넘긴다. */
@media (min-width: 768px) {
  .pub .rotbox > .cap h2 { font-size: 30px; border-left-width: 5px; padding-left: var(--s3); }
}
@media (min-width: 1024px) {
  .pub .rotbox > .cap h2 { font-size: 48px; }
  /* ---- 회원 앱에서만 남아 있던 제목 크기 구멍 (v1.66.0)
   * v1.60.0 은 위 주석에서 "`.certs > h2` 는 home.css 의 `.sect > h2` 가 이미 이긴다"
   * 고 적었는데, 그것은 **소개 홈페이지에서만** 참이다. `home.css` 는 `pageCss` 로
   * 넘기는 화면 전용 스타일시트이고 지금 그것을 읽는 화면은 `home.ejs` · `dues.ejs`
   * 둘뿐이다. 회원 홈(`matches.ejs`)은 `include("../partials/head")` 를 인자 없이
   * 부르므로 home.css 가 **아예 안 실려서** 그 규칙이 존재하지 않는다.
   * 그래서 회원 홈 1024px 이상에서 실측(운영 CSS, 진짜 Chromium):
   *     390  certs 20px · 후원사 20px   (같다, 손댈 것 없음)
   *     1097 certs 20px · 후원사 30px   ← 같은 등급 제목이 한 화면에 두 크기
   *     1440 certs 20px · 후원사 30px
   * 큰 쪽을 줄이지 않고 작은 쪽을 같은 계단에 올린다. 값 · 왼쪽 선을 바로 위
   * `.wrap .rotbox > .cap h2` 와 **한 줄에서** 같게 맞춘다(같은 뜻이면 같은 모양).
   * ★`.pub` 을 여기에 넣지 말 것★ — 소개 홈페이지의 이 제목은 48px 이고, `.wrap` 밖에
   * 있어 이 선택자에 걸리지 않는다(실측으로 확인함: 소개 홈 1097 · 1440 에서 48px 유지).
   * 767px 이하는 대상이 아니다(위 실측의 390 줄). */
  .wrap .rotbox > .cap h2,
  .wrap .certs > h2 { font-size: 30px; border-left-width: 5px; padding-left: var(--s3); }
}

/* 진짜 가로 스크롤이다. 브라우저가 원래 하던 스와이프와 관성이 그대로 먹고,
   자바스크립트가 죽어도 손가락으로 넘길 수 있다. 직접 만든 드래그로 바꾸지 말 것. */
.rotbox .rot-view {
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none; -ms-overflow-style: none;
}
.rotbox .rot-view::-webkit-scrollbar { display: none; }
.rotbox .rot-track { display: flex; }
/*
 * 한 판에 몇 칸을 세울지는 **CSS 가 정한다** (v1.26.0).
 *
 * 대표님이 그대로 적어 보내신 말: **"협력기관이도 가맹점이고 한번에 3개씩 나오게는
 * 되는건가요"**, **"후원사도 마찮가지입니다"**.
 *
 * v1.15.0~v1.25.0 은 `flex: 0 0 100%` 였다. 한 칸이 곧 상자 폭이라 폰에서는 맞았지만,
 * 넓은 화면에서도 로고 하나가 1280px 짜리 벽 한가운데 서 있었다. 이제 `--per` 한 칸으로
 * 폭마다 몇 개를 세울지 정한다. **자바스크립트는 이 값을 읽지 않는다** - 실제로 그려진
 * 칸 폭을 재서 알아낸다(public/js/rotator.js). 그래야 CSS 만 고쳐도 넘김이 따라온다.
 *
 * 기본값은 여전히 **1칸**이다. 지정서·인증서 상자(.certs)는 글자를 읽는 그림이라
 * 1칸 그대로 두어야 한다. 3칸으로 만들고 싶은 상자만 `multi` 클래스를 붙인다.
 *
 * `--slide-gap` 은 칸 사이 간격이다. 칸 안쪽 좌우 여백으로 만든다(box-sizing 이
 * border-box 라 칸 폭은 정확히 1/n 로 유지된다). track 에 gap 을 주면 칸 폭이
 * 1/n 에서 어긋나서 "한 판 = 칸폭 x n" 이라는 넘김 계산이 깨진다. gap 으로 바꾸지 말 것.
 */
.rotbox { --per: 1; --slide-gap: 0px; }
.rotbox .rot-slide {
  flex: 0 0 calc(100% / var(--per));
  width: calc(100% / var(--per));
  padding-left: calc(var(--slide-gap) / 2);
  padding-right: calc(var(--slide-gap) / 2);
  scroll-snap-align: start;
}
/* 태블릿 폭에서 2칸. 360~767px(폰)은 위 기본값 그대로 **1칸**이다 - 어르신 폰에서
   카드 셋을 나란히 세우면 글자가 읽히지 않는다. */
@media (min-width: 768px) {
  .rotbox.multi { --per: 2; --slide-gap: 14px; }
}
/* 대표님이 요청하신 "한번에 3개씩" 이 걸리는 폭이다. */
@media (min-width: 1024px) {
  .rotbox.multi { --per: 3; --slide-gap: 16px; }
}
/* v1.31.0 에서 여기 `@media (min-width: 1600px) { .rotbox.multi { --per: 4 } }` 를
 * 넣었다가 **도로 뺐다.** 회원 앱 본문이 1560px 까지 넓어져 카드가 커 보였기 때문인데,
 * 실측해 보니 이 규칙이 회원 앱뿐 아니라 **소개 홈페이지의 후원사 띠까지 같이** 바꿨다
 * (1920px 에서 문서 높이 5243 → 5185). `.rotbox.multi` 는 두 화면이 함께 쓰는 부품이라
 * 폭 하나 올리려다 대표님이 이미 확인하신 화면을 건드리게 된다.
 *
 * 그리고 3칸은 우리가 고른 숫자가 아니라 대표님이 그대로 적어 보내신 요청이다:
 * **"협력기관이도 가맹점이고 한번에 3개씩 나오게는 되는건가요", "후원사도 마찮가지입니다"**.
 * 넓다는 이유로 이 숫자를 올리지 말 것. 올리려면 대표님께 먼저 여쭤야 한다. */

/* [이전] [3 / 7] [다음]. 점(dot) 을 쓰지 않는다 — 이 클럽 기준에서 점은 못 누른다.
   버튼 높이 52px 은 엄지로 누르는 최소치다. 이 줄은 자바스크립트가 살아 있을 때만
   나타난다(rotator.js 가 hidden 을 벗긴다). 눌리지 않는 버튼을 남기지 않는다.
   가운데 숫자는 **판(page) 번호**다(v1.26.0). 3칸씩 도는 상자에서 칸 번호를 세면
   [다음] 을 눌러도 숫자가 3씩 뛰거나 제자리인 것처럼 보인다. 넓은 화면에서 한 판에
   다 들어가면(판이 하나뿐이면) rotator.js 가 이 줄을 도로 감춘다. */
.rotbox .rot-nav {
  display: flex; align-items: center; gap: 10px; margin-top: 10px;
}
.rotbox .rot-nav[hidden] { display: none; }
.rotbox .rot-btn {
  flex: 1 1 0; min-height: var(--tap); padding: 0 var(--s4);
  border: 1.5px solid var(--line); border-radius: var(--r-sm); background: var(--card);
  color: var(--ink); font-family: inherit; font-size: var(--fs-md); font-weight: 800;
  cursor: pointer;
}
.rotbox .rot-btn:active { background: var(--card-2); }
.rotbox .rot-count {
  flex: 0 0 auto; min-width: 62px; text-align: center;
  font-size: var(--fs-base); font-weight: 700; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.rotbox .rot-count b { color: var(--ink); font-weight: 800; }
.rotbox > .sub-actions { margin-top: 12px; }

.sponsor {
  display: flex; align-items: center; gap: 13px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  padding: 12px 14px; min-height: 116px; color: var(--ink);
}
/*
 * 로고 칸이 v1.15.0 에서 64px → 96px 로 커졌다.
 *
 * 고객 말 그대로: "후원박스 사진도 들어갈수있게 하고". 후원사들이 올리는 것은 깔끔한
 * 로고만이 아니라 **현수막·모집 포스터 사진**이다(지금 올라와 있는 든든한의원이 그렇다).
 * 64px 안에서는 그런 사진의 글자가 아예 읽히지 않았다. 세로로 쌓던 때는 카드가 여러 장
 * 붙어 있어 키울 수 없었지만, 한 번에 한 곳만 서 있는 지금은 자리가 있다.
 * **더 줄이지 말 것.** 줄이면 사진을 올리는 의미가 없어진다.
 *
 * ---------------------------------------------------------------------------
 * v1.21.0: 여기 있던 주석이 거짓말이었고, 로고는 **실제로 잘리고 있었다.**
 * ---------------------------------------------------------------------------
 *
 * 옛 코드는 `display:grid; place-items:center` 상자 안에서 `img { height: 100% }`
 * 였다. 그런데 **grid 칸의 높이는 auto** 라 백분율 높이가 풀리지 않고 `auto` 로
 * 떨어진다. 그래서 이미지는 원본 비율대로 세로가 길어졌고(94px 폭에 실측 세로
 * 125.33 / 141 / 197 / 201px), `overflow:hidden` 이 그 초과분을 **잘라 냈다.**
 * 든든한의원 로고는 위아래 29px 이 사라진 채 나가고 있었다.
 *
 * 같은 함정을 이 파일이 이미 한 번 적어 두었다: `.afflstrip .affl .mk` 주석이
 * "grid 가 아니라 flex 다" 라고 말하는 그 이야기다. 여기만 안 고쳐져 있었다.
 *
 * 고친 방법도 그 자리와 같다: **flex 상자 + max-width/max-height.**
 * flex 상자는 높이가 확정(94px)이라 백분율이 제대로 풀리고, `width/height: auto`
 * 는 원본 비율을 지키므로 세로로 긴 사진도 가로로 긴 사진도 잘리지 않고 들어간다.
 * `height: 100%` 로 되돌리지 말 것. 되돌리는 순간 다시 잘린다.
 */
.sponsor .logo {
  flex: 0 0 auto; width: 96px; height: 96px; border-radius: var(--r-sm);
  background: var(--card); border: 1px solid var(--line); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.sponsor .logo img {
  /* v1.58.0: 칸 크기로 그린다(width/height 100%). 파일이 정사각(squarePad)이므로
     후원사 띠 · 팀 띠의 로고가 전부 같은 면적으로 그려진다(「스폰서 사진이 각각
     사이즈가달라요」). 협력기관 띠(v1.50.0)가 같은 방법으로 고쳤다. `width: auto` 로
     되돌리면 원본 비율이 그대로 크기가 되어 다시 어긋난다. */
  width: 100%; height: 100%;
  object-fit: contain; object-position: center; display: block;
}
/* 로고를 아직 안 올린 후원사는 이름 첫 글자로 대신한다(빈 네모를 보여 주지 않는다). */
.sponsor .logo .ini { font-size: 30px; font-weight: 800; color: var(--ink-2); }
.sponsor .tx { min-width: 0; flex: 1; }
.sponsor .tx .nm { display: block; font-size: var(--fs-control); font-weight: 800; line-height: 1.3; }
.sponsor .tx .ln { display: block; font-size: var(--fs-base); color: var(--ink-2); line-height: 1.45; margin-top: 3px; }
/* 누를 수 있는 후원사만 화살표를 붙인다. 눌리지 않는 것에 화살표를 붙이면 어르신이 계속 누른다.
   화살표는 뜻을 혼자 지지 않는다 — 카드 전체가 그 후원사로 가는 링크이고, 이름이 옆에 있다. */
.sponsor .go { flex: 0 0 auto; color: var(--ink-2); padding-left: 4px; }
.sponsor .go .i { width: 22px; height: 22px; }
a.sponsor:active { background: var(--card-2); }

/* ------------------------------------------- 후원사 띠는 **한 판에 여섯 칸**이다 (v1.54.0)
 *
 * 대표님이 그대로 적어 보내신 말:
 *   「후원업체는 6칸으로 나오게끔 돌아가게 20개정도는 들어갈수있게 (...)
 *    홈페이지 주소 링크걸수있게 부탁」
 *   「작게해도됩니다 눌르면 후원사 홈페이지에 링크되게 해주시면됩니다」
 *
 * ### 왜 위의 `.sponsor` 를 고치지 않고 여기에 따로 적었나
 *
 * `.sponsor` 카드는 **가입된 팀 띠(`views/partials/teams.ejs`)가 같이 쓴다.** 위 규칙을
 * 직접 6칸용으로 줄이면 대표님이 이미 확인하신 팀 띠까지 같이 작아진다(v1.31.0 에 폭
 * 하나 올리려다 소개 홈페이지 후원사 띠를 같이 바꾼 그 사고와 같은 모양이다).
 * 그래서 **`.sponsors`(상자) 안에서만** 덮어쓴다. 팀 띠는 예전 가로 카드 그대로다.
 *
 * ### 여섯 칸 = 가로 3칸 x 2줄
 *
 * 폰(360~390px)에 카드 여섯을 **한 줄로** 세우면 한 칸이 55px 이라 로고도 이름도
 * 아무것도 안 보인다. 그래서 가맹점 띠가 v1.50.0 에 쓴 방법을 그대로 쓴다:
 * **가로 3칸 x 2줄 격자.** 대표님이 세시는 "6칸" 은 그대로 여섯이고, 한 칸은 폰에서도
 * 이름이 읽히는 폭으로 남는다.
 *
 * 줄을 늘리는 방법이 grid 인 이유, 그리고 ★칸 폭을 **열**(grid-auto-columns)이 쥐는
 * 이유는 아래 `.merchants .rot-track` 주석에 길게 적혀 있다. 같은 함정이므로 같이 읽을 것
 * (백분율 폭이 auto 열을 기준으로 풀리면 카드가 내용 폭까지 쪼그라든다).
 * 줄 수는 마크업의 `data-rot-rows="2"` 가 rotator.js 에 알려 준다.
 *
 * `--per: 3` 을 **모든 폭에서** 적는다. 위쪽 `.rotbox.multi` 의 미디어 쿼리(1 / 2 / 3)를
 * 그대로 두면 폰에서 한 판이 1 x 2 = 두 칸이 되어 "6칸" 이 아니게 된다. 두 규칙의
 * 특정도가 같으므로(둘 다 클래스 둘) **이 블록이 파일에서 뒤에 있어야** 이긴다.
 * 위로 옮기지 말 것.
 *
 * 20곳이면 한 판 6칸 x 4판이고, 마지막 판은 두 칸이 빈다(rotator.js 가 마지막 판을
 * 끝까지 밀고 멈춘다. `maxLeft()` 주석 참조).
 */
.rotbox.sponsors { --per: 3; --slide-gap: 8px; }
@media (min-width: 768px) { .rotbox.sponsors { --per: 3; --slide-gap: 12px; } }
.sponsors .rot-track {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, auto);
  grid-auto-columns: calc(100% / var(--per));
  align-items: stretch;
  row-gap: 8px;
}
/* 두 줄이 되면 위·아래 카드 높이가 다를 때 줄이 들쭉날쭉해진다. 칸을 늘려서 맞춘다
   (가맹점 띠와 같은 두 줄). */
.sponsors .rot-slide { display: flex; width: auto; }
.sponsors .rot-slide > .sponsor { width: 100%; }
/*
 * 칸이 좁아졌으므로 카드가 **세로 타일**이다: 로고가 위, 이름이 아래.
 *
 * 가로로 긴 예전 카드(로고 옆에 글자)를 3칸 폭에 밀어 넣으면 글자 자리가 60px 남짓밖에
 * 안 남아서 후원사 이름이 한 글자씩 끊긴다. 세로로 세우면 이름이 칸 폭을 통째로 쓴다.
 *
 * `min-height` 를 남겨 두는 이유: 로고를 아직 안 올린 후원사와 올린 후원사가 같은 줄에
 * 서도 타일 높이가 같아야 한다. 높이가 칸마다 다르면 5초마다 판이 넘어갈 때 화면이
 * 위아래로 튄다(가맹점 혜택 문구가 v1.40.0 에 같은 이유로 한 줄로 잘렸다).
 */
.sponsors .sponsor {
  flex-direction: column; justify-content: flex-start; gap: 7px;
  padding: 10px 8px; min-height: 124px; text-align: center;
}
/* 로고 72px. 예전 96px 에서 줄인 것은 대표님이 **「작게해도됩니다」** 라고 직접 풀어
   주신 제약이다. 그 아래로 더 줄이지 말 것 - 후원사가 올리는 것은 깔끔한 로고만이
   아니라 현수막·모집 포스터 사진이고(위 v1.15.0 주석), 그런 사진은 이 크기가 바닥이다.
   `.sponsor .logo` 와 같은 flex 상자 그대로다(grid 로 바꾸면 v1.21.0 처럼 다시 잘린다). */
.sponsors .sponsor .logo { width: 72px; height: 72px; }
/* v1.60.0 — 넓은 화면에서는 서류 칸과 **같은 값**(`--doc-pic`)으로 같이 커진다.
   「인정서는 후원사 사이즈로 통일」(v1.46.0)이 커진 뒤에도 지켜지는 방법이다.
   실측: 1097 에서 로고 118x118 · 칸 313x191@67 → 그림도 118x118, 1920 에서 로고
   198x198 · 칸 521x271.

   v1.61.0 - **문턱을 1024 에서 768 로 내렸다.** `--doc-pic` 자체가 768 에서 켜지는데
   후원사만 1024 에서 켜고 있어서, 태블릿(768)에서 서류 칸 120px 옆에 후원사 로고가
   72px 로 서 있었다(실측 120x120 vs 72x72, 1.67배). 「인정서는 후원사 사이즈로 통일」
   (2026-09-14)을 정확히 어기는 한 칸이다. 이제 두 구역이 **같은 문턱에서 같은 값**을
   읽는다. 1024 단계를 따로 두지 않는다 - `--doc-pic` 이 이미 768/1600 두 단계다.
   폰(390)은 여전히 이 규칙 밖의 72px 이다. 그 값은 대표님이 **「작게해도됩니다」**
   (2026-09-17)로 직접 풀어 주신 값이고, 390 에서 한 칸 폭이 130px(안쪽 114px)이라
   96px 로 올리면 로고 좌우 여백이 9px 밖에 남지 않는다. ★72px 아래로 내리지 말 것★.
   ★`--per` 는 건드리지 않았다★ — 3칸 x 2줄 = 6칸은 대표님이 숫자로 말씀하신 값이고
   v1.31.0 에 한 번 올렸다가 되돌렸다. 위 주석의 금지가 여전히 유효하다. */
@media (min-width: 768px) {
  .sponsors .sponsor .logo { width: var(--doc-pic, 72px); height: var(--doc-pic, 72px); }
}
/* 후원사가 한 판(6칸)보다 적을 때 줄을 가운데로 모은다 (v1.60.0).
   「띠가 벽 왼쪽 3분의 1 에 몰려 있다」로 올라온 건은 CSS 고장이 아니라 **기록이 2개인데
   칸이 3x2** 라서 생긴 모양이었다. 둘 다 첫 열에 세로로 쌓이니 왼쪽에 붙어 보인다.
   실측(후원사 2곳): 1920 에서 첫 칸 @166 → @699, 1097 에서 @67 → @392.
   띠가 실제로 넘칠 만큼 차면 이 규칙은 **아무 일도 하지 않는다** — 12칸으로 복제해서
   확인했다(viewW 1600 · scrollW 3200, 첫 칸이 다시 @166).
   ★`safe` 를 빼지 말 것★, 맨 `center` 는 넘칠 때 첫 칸을 왼쪽 밖으로 잘라 낸다.

   v1.63.0, 문턱(1024)을 뺐다. 대표님: 「후원사도 6개나오게 하면되고」. 운영 후원사는
   3곳이라 폰(390)에서 한 판 6칸 중 두 칸에 서고 나머지 네 칸이 빈다. 열 흐름(column
   flow) 격자는 3 · 4 번째 칸을 두 번째 열 아래로 쌓아 **세 번째 열을 통째로 비워**
   띠가 왼쪽에 붙은 것처럼 보였고, 협력기관 띠(10곳, 판이 꽉 참)와 나란히 놓으면 두 띠가
   다르게 보이는 그 차이였다. 가운데 모으기는 협력기관 띠에도 1024 에서 이미 있다
   (`.afflstrip .rot-track`). 판이 꽉 차면 이 규칙은 여전히 아무 일도 하지 않으므로
   채워진 띠의 자리는 한 픽셀도 움직이지 않는다(위 12칸 실측). */
.sponsors .rot-track { justify-content: safe center; }
/*
 * v1.58.0 — 로고 그림이 **칸과 같은 크기**로 그려진다 (위쪽 `.sponsor .logo img` 가
 * `width/height: 100%` 로 바뀌었다). 대표님: 「스폰서 사진이 각각 사이즈가달라요」.
 * 예전의 `auto` 값은 원본 비율이 그대로 그림 크기가 되게 둬서 480x439 와 480x480 이
 * 섞인 날 크기가 어긋났다. 업로드가 파일을 정사각으로 저장하므로(squarePad) 이제
 * 어떤 그림도 같은 면적으로 들어간다. 협력기관 띠가 v1.50.0 에 같은 방법으로 고쳤다.
 */
.sponsors .sponsor .logo .ini { font-size: 24px; }
.sponsors .sponsor .tx { flex: 0 1 auto; width: 100%; }
/* 이름은 두 줄까지 보여 주고 그 아래로는 자른다. 좁은 칸에서 긴 상호가 타일을 혼자
   세로로 늘리는 것을 막는다. `-webkit-line-clamp` 는 이 클럽 회원이 쓰는 웹뷰에서도
   도는 값이고, 안 먹는 브라우저에서는 그냥 다 보일 뿐이라 아무것도 잃지 않는다. */
.sponsors .sponsor .tx .nm {
  font-size: var(--fs-sm); line-height: 1.3; word-break: keep-all;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.sponsors .sponsor .tx .ln {
  font-size: var(--fs-xs); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 「홈페이지」 한 줄. **누를 수 있다는 것을 글자로 말하는 자리다**(아이콘 금지, 폰에는
   hover 가 없다). 주소를 넣은 후원사 칸에만 선다 - 없는 칸에 붙이면 눌러도 아무 일이
   안 일어나는 자리를 어르신이 계속 누르게 된다. 밑줄까지 넣어 링크로 읽히게 한다. */
.sponsors .sponsor .tx .go-tx {
  color: var(--turf-dark); font-weight: 800; text-decoration: underline; text-underline-offset: 3px;
}
/* 누름 자리 바닥. 타일이 작아져도 손가락 하나가 닿는 크기 아래로 내려가지 않는다. */
.sponsors a.sponsor { min-height: max(124px, var(--tap)); }

/* ------------------------------------------------------------------ 종목 사진 관리 (v1.15.0)
 *
 * 운영자 화면(`/admin/sport-photos`). 종목 하나 = 카드 한 장 = 폼 하나다.
 * **지금 회원 화면에 나가고 있는 그림을 그대로 왼쪽에 보여 준다.** 무엇이 바뀌는지
 * 글로 설명하는 대신 눈으로 보게 하는 편이 이 사용자층에게 훨씬 빠르다.
 */
.card.sportphoto .row { display: flex; align-items: center; gap: 14px; margin-bottom: 12px; }
.card.sportphoto .shot {
  flex: 0 0 auto; width: 96px; height: 84px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--card); overflow: hidden;
  display: grid; place-items: center;
}
.card.sportphoto .shot img { display: block; width: 100%; height: 100%; }
/* 손그림은 여백 안에, 사진은 꽉 채워서. 회원 화면(.sportpick .pic)과 같은 규칙이다. */
.card.sportphoto .shot img.draw { object-fit: contain; padding: 4px; }
.card.sportphoto .shot img.photo { object-fit: cover; }
/* Venue photo box (v1.75.0) hides the empty preview with [hidden]; the display:block above would
   otherwise win and paint a broken-image icon. */
.card.sportphoto .shot img[hidden] { display: none; }
.card.sportphoto .grow { flex: 1; min-width: 0; }
.card.sportphoto .state { margin: 0 0 10px; line-height: 1.45; }

/* ------------------------------------------------------------------ 가맹점 카드 (v1.15.0)
 *
 * 후원사 상자 바로 아래에 같은 모양의 상자가 하나 더 선다. **두 상자가 똑같이 생기면
 * 회원이 "포인트 쓸 수 있는 가게" 와 "클럽을 도와주는 곳" 을 구별하지 못한다.**
 * 그래서 색을 갈랐다: 후원사는 흰 카드 + 회색 테두리, 가맹점은 **초록 테두리 + 연한
 * 초록 바탕**(포인트를 쓰는 곳 = 클럽의 주 색).
 *
 * 왼쪽 칸은 v1.26.0 에서 **가게 사진**이 되었다(대표님 말 그대로 "가맹점도 사진올리수
 * 있도록되나요"). 사진을 안 올린 가게는 예전 그대로 **이름 첫 글자**다 — 지금 등록된
 * 네 곳이 전부 그 상태이고, 그 낙하산이 없으면 사진 없는 가게만 빈 네모가 된다.
 */
.merchant {
  display: flex; align-items: center; gap: 13px;
  background: var(--turf-bg); border: 1.5px solid var(--turf); border-radius: var(--r);
  padding: 12px 14px; min-height: 84px; color: var(--ink);
}

/* ------------------------------------------------- 가맹점 상자는 **두 줄**이다 (v1.50.0)
 *
 * 대표님: **「가맹점은 3개씩 *2 개」**. 한 판에 3곳이던 것을 **3곳 x 2줄 = 6곳**으로
 * 늘린다(폰은 1곳 x 2줄 = 2곳. 390px 에 카드 셋을 나란히 세우면 가게 이름이 안 읽힌다,
 * `--per` 를 정한 v1.26.0 주석의 그 이유가 그대로 유효하다).
 *
 * 줄을 늘리는 방법이 **grid 인 이유**: 트랙을 `grid-auto-flow: column` 두 줄짜리 격자로
 * 두면 칸이 「위·아래 → 다음 칸」 순서로 채워진다. 그래서 가로로 미는 거리는 그대로
 * `칸 폭 x 보이는 칸 수` 이고, rotator.js 의 판 계산(칸 폭을 재는 부분)을 한 줄도
 * 안 고쳐도 된다. 판에 몇 칸이 들어가는지만 `data-rot-rows="2"` 로 알려 준다.
 *
 * `.rotbox .rot-track { display: flex }` 를 **덮어쓰는 것이라 순서상 뒤에 있어야 한다.**
 *
 * ★칸 폭을 **열(grid-auto-columns)** 이 쥔다★ - `.rot-slide` 의 `width: calc(100%/--per)`
 * 가 아니다. 격자 칸에서 백분율 폭은 **자기 열**을 기준으로 풀리는데, 열이 `auto`(내용
 * 크기)면 서로가 서로를 기다리는 순환이 되어 **카드가 내용 폭까지 쪼그라든다.**
 * 실제로 1280px 에서 가게 이름 위에 [전화] 버튼이 겹쳐 그려졌다(v1.50.0 작업 중 실측).
 * 그래서 열에 폭을 적고 칸은 `width: auto` 로 풀어 준다. **되돌리지 말 것.**
 * `--per` 는 그대로 한 곳(위 미디어 쿼리)에서만 정해진다.
 */
.merchants .rot-track {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, auto);
  grid-auto-columns: calc(100% / var(--per));
  align-items: stretch;
  row-gap: 10px;
}
/* 두 줄이 되면 위·아래 카드 높이가 다를 때 줄이 들쭉날쭉해진다. 칸을 늘려서 맞춘다. */
.merchants .rot-slide { display: flex; width: auto; }
.merchants .rot-slide > .merchant { width: 100%; }
.merchant .mark {
  flex: 0 0 auto; width: 58px; height: 58px; border-radius: var(--r-sm);
  background: var(--card); border: 1.5px solid var(--turf);
  display: grid; place-items: center;
  font-size: 26px; font-weight: 800; color: var(--turf-dark);
}
/* 사진이 붙은 칸 (v1.26.0). 글자 칸(58px)보다 크다 — 가게 사진은 간판이나 가게 앞
   사진이라 58px 안에서는 무엇을 찍은 것인지 알 수 없다(후원사 로고를 64 → 96px 로
   키운 v1.15.0 과 같은 이유다). **`cover` 로 바꾸지 말 것**: 가게가 올리는 사진에는
   상호와 전화번호가 가장자리에 적혀 있는 경우가 많고, 그때 정확히 그 글자가 잘린다. */
.merchant .mark.photo { width: 76px; height: 76px; padding: 3px; overflow: hidden; }
.merchant .mark.photo img {
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  object-fit: contain; display: block; border-radius: 6px;
}
.merchant .tx { min-width: 0; flex: 1; }
.merchant .tx .nm { display: block; font-size: var(--fs-control); font-weight: 800; line-height: 1.3; }
.merchant .tx .ln { display: block; font-size: var(--fs-base); color: var(--ink-2); line-height: 1.45; margin-top: 3px; }
/* [전화] 는 카드 안의 유일한 누를 것이다. 아이콘이 아니라 글자로 둔다. */
.merchant .call { flex: 0 0 auto; min-height: var(--tap); display: inline-flex; align-items: center; }

/* ------------------------------------------------------------------ 부관리자 (v1.9.0)
 *
 * 대표님이 폰에서 회원을 찾아 부관리자로 올리는 화면과, 부관리자가 대표 전용 화면을
 * 눌렀을 때 나오는 안내 화면. **표를 쓰지 않는다** — 360px 에서 이름·번호·버튼이
 * 한 줄에 들어가지 않아 글씨를 줄이게 되고, 그러면 이 클럽 기준을 깬다.
 * 사람 한 명 = 카드 한 장, 버튼은 카드 폭 전체(한 손 엄지로 누른다).
 */

.card.slots { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; border-color: var(--turf); background: var(--turf-bg); }
.card.slots .n { display: flex; align-items: baseline; gap: 5px; flex: 0 0 auto; }
.card.slots .n .big { font-size: 40px; font-weight: 800; line-height: 1; color: var(--turf-dark); }
.card.slots .n .of { font-size: var(--fs-control); font-weight: 800; color: var(--ink-2); }
.card.slots .ln { flex: 1 1 190px; min-width: 0; font-size: var(--fs-base); line-height: 1.5; color: var(--ink); }
.card.slots.full { border-color: var(--warn); background: var(--warn-bg); }
.card.slots.full .n .big { color: var(--warn); }

.card.op { margin-top: 12px; }
.card.op.owner { border-color: var(--turf); }
.card.op .hd { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.card.op .hd .nm { font-size: 21px; font-weight: 800; }
.card.op .sub { font-size: var(--fs-base); color: var(--ink-2); margin-top: 6px; }
.card.op .note { font-size: var(--fs-base); line-height: 1.5; color: var(--ink-2); background: var(--card-2); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 11px 13px; margin-top: 11px; }
.card.op form { margin-top: 12px; }

.card.owner-only { text-align: center; }
/* 예전에는 자물쇠 이모지 한 글자였다. 선 그림으로 바꿨고, 아래 제목이 같은 말을 글자로 한다. */
.card.owner-only .ic { display: block; width: 46px; height: 46px; margin: 0 auto var(--s3); color: var(--ink-2); }
.card.owner-only .hd { font-size: var(--fs-xl); font-weight: 800; margin-bottom: var(--s3); }
.card.owner-only .ln { font-size: var(--fs-md); line-height: 1.6; margin-bottom: 10px; }
.card.owner-only .ln.muted { color: var(--ink-2); }
/* **카드 안에 카드를 넣지 않는다 (v1.18.0).** 여기 있던 `.card.sub` 는 카드 속 카드였고
   (이번 기준의 명시적 금지) 화면에서도 테두리가 두 겹이라 어디까지가 이 화면의 본문인지
   흐렸다. 같은 자리를 **선 하나로 가른 안쪽 구획**으로 바꾼다. 테두리를 다시 두르지 말 것. */
.card.owner-only .panel {
  text-align: left; margin: var(--s5) 0 var(--s4);
  padding-top: var(--s4); border-top: 1px solid var(--line);
}
.card.owner-only .panel h3 { font-size: var(--fs-md); font-weight: 800; margin: 0 0 var(--s2); }
.card.owner-only .hero-action { margin-top: 6px; }

/* ------------------------------------------------------------------ 개인정보 동의 (v1.10.0)
 *
 * **깨뜨리면 안 되는 제품 제약 (약하게 고치지 말 것)**
 *   - 이 클럽 회원 다수는 **어르신**이다. 동의 문구는 본문 크기(17px 이상)로 읽힌다.
 *     10px 회색 약관 글씨로 줄이는 순간 "읽고 동의했다" 가 거짓이 된다.
 *   - 체크 칸은 **한 손 엄지로 누르는 크기**다. 줄 전체가 탭 영역이고 높이 72px,
 *     체크 상자 자체도 30px 다. 작은 네모 하나만 누르게 만들지 말 것.
 *   - **390px 과 360px 두 폭에서 한 손으로 쓸 수 있어야 한다.** 가로 스크롤 금지,
 *     고정 너비 금지, 한글은 `word-break: keep-all` 로 단어 중간에서 자르지 않는다.
 *   - 가입은 여전히 **한 화면**이다. 동의 때문에 단계나 팝업을 늘리지 않는다.
 *     (window.alert/confirm 은 카카오·네이버 인앱 브라우저가 삼킨다. 절대 쓰지 않는다.)
 */

.consent { border: 2px solid var(--turf); background: var(--turf-bg); border-radius: var(--r); padding: var(--s4); margin-bottom: 18px; }
.consent .hd { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; font-size: var(--fs-control); font-weight: 800; color: var(--ink); margin-bottom: 10px; }
.consent .req { font-size: var(--fs-sm); font-weight: 800; color: #fff; background: var(--warn); border-radius: 6px; padding: 4px 9px; }
.consent .sum { display: grid; gap: 9px; margin: 0 0 13px; padding: 0; list-style: none; }
.consent .sum li { font-size: var(--fs-base); line-height: 1.55; color: var(--ink); word-break: keep-all; }
.consent .sum li b { display: block; font-size: var(--fs-sm); font-weight: 800; color: var(--turf-dark); margin-bottom: 2px; }

/* 줄 전체가 탭 영역이다. 체크 상자만 누르게 만들면 어르신 손가락으로는 못 맞춘다. */
.consent-check {
  display: flex; align-items: center; gap: 14px; min-height: 72px;
  padding: 12px 15px; border: 2px solid var(--turf); border-radius: var(--r);
  background: var(--card); cursor: pointer;
}
.consent-check input { flex: 0 0 auto; width: 30px; height: 30px; accent-color: var(--turf); margin: 0; }
.consent-check span { font-size: var(--fs-lg); font-weight: 800; color: var(--ink); line-height: 1.4; word-break: keep-all; }
.consent-check:has(input:checked) { background: var(--turf-bg); }
.consent-check:has(input:checked) span { color: var(--turf-dark); }
.consent-check input:focus-visible { outline: 3px solid var(--turf); outline-offset: 3px; }

.consent-more {
  display: inline-flex; align-items: center; min-height: var(--tap); margin-top: 10px;
  font-size: var(--fs-base); font-weight: 700; color: var(--turf-dark);
  text-decoration: underline; text-underline-offset: 4px;
}

/* 기존 회원에게 다음 방문 때 한 번만 뜨는 조용한 안내.
   화면을 막지 않는다(모달 금지). 누르지 않아도 아래 경기 목록을 그대로 쓸 수 있다. */
.consent-hold { max-width: var(--app-max); margin: 0 auto; padding: 14px 14px 0; }
.consent-hold.wide { max-width: max(1080px, var(--app-max)); }
.consent-notice { border: 2px solid var(--turf); background: var(--card); border-radius: var(--r); padding: 16px 15px; margin-bottom: 14px; }
.consent-notice .hd { font-size: var(--fs-lg); font-weight: 800; margin-bottom: 8px; word-break: keep-all; }
.consent-notice p { font-size: var(--fs-base); line-height: 1.6; color: var(--ink-2); margin: 0 0 13px; word-break: keep-all; }
.consent-notice p b { color: var(--ink); font-weight: 800; }
.consent-notice .sub-actions { margin-top: 10px; }

/* 내 정보의 동의 상태 칸 */
.consent-state { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: var(--fs-md); font-weight: 800; margin-bottom: 8px; }
.consent-state .when { font-size: var(--fs-base); font-weight: 700; color: var(--ink-2); }
.card.consent-card.off { border-color: var(--hold); background: var(--hold-bg); }
.card.consent-card .ln { font-size: var(--fs-base); line-height: 1.6; color: var(--ink-2); margin: 6px 0 0; word-break: keep-all; }

/* 방침 본문. 서류가 아니라 읽는 글이다. 글씨를 더 줄이지 말 것. */
/* 방침 본문. 서류가 아니라 읽는 글이다. 구역 제목에 왼쪽 선을 넣어 어디서 새 조항이
   시작하는지 눈으로 잡히게 했다(본문 화면의 .sect-head 와 같은 규칙이다). */
.policy h2 { font-size: var(--fs-lg); font-weight: 800; margin: var(--s6) 0 var(--s2); word-break: keep-all; border-left: 4px solid var(--ink); padding-left: 10px; }
.policy h2:first-child { margin-top: 6px; }
.policy p, .policy li { font-size: var(--fs-base); line-height: 1.65; color: var(--ink); word-break: keep-all; }
.policy p { margin: 0 0 10px; }
.policy ul { margin: 0 0 12px; padding-left: 20px; display: grid; gap: 7px; }
.policy b { font-weight: 800; }
.policy .tbl { display: grid; gap: 10px; margin: 0 0 12px; }
.policy .tbl .row { border: 1px solid var(--line); border-radius: var(--r-sm); padding: 12px 13px; background: var(--card-2); }
.policy .tbl .row .k { display: block; font-size: var(--fs-sm); font-weight: 800; color: var(--turf-dark); margin-bottom: 4px; }
.policy .tbl .row .v { font-size: var(--fs-base); line-height: 1.55; }

/* ------------------------------------------------------------------ 경기 개설 권한 (v1.11.0)
 *
 * 회원도 경기를 열 수 있게 할지 켜고 끄는 운영자 화면과, 꺼져 있을 때 회원 홈에
 * 버튼 대신 들어가는 한 줄이다.
 *
 * **토글 스위치를 만들지 말 것.** 이 클럽 운영자도 어르신이다. 켜짐/꺼짐을 색과
 * 위치로만 표시하면 지금 어느 쪽인지 읽는 데 시간이 걸리고, 결국 잘못 눌러 놓고
 * 고장 났다고 연락이 온다. 상태는 **글자로 크게**, 바꾸는 것은 **버튼 하나**다.
 */
.card.switch-state { border-width: 2px; }
.card.switch-state .k { font-size: var(--fs-sm); font-weight: 800; color: var(--ink-2); margin-bottom: 4px; }
.card.switch-state .v { font-size: 25px; font-weight: 800; line-height: 1.25; margin-bottom: 8px; }
.card.switch-state .ln { font-size: var(--fs-base); line-height: 1.6; margin: 0; word-break: keep-all; }
.card.switch-state.on { border-color: var(--turf); background: var(--turf-bg); }
.card.switch-state.on .v { color: var(--turf-dark); }
.card.switch-state.off { border-color: var(--hold); background: var(--hold-bg); }
.card.switch-state.off .v { color: var(--hold); }

/* 종목 표시 관리(v1.44.0). 한 줄 안에서 상태와 동작을 같이 읽는다.
   v1.63.0 별명 칸이 같은 줄에 늘었다. 보이기 토글과 달리 칸 하나 + 저장 단추라
   grow 안에서 세로로 쌓되, 단추는 칸 오른쪽에 나란히 붙인다(모바일 360px 까지). */
.sport-visibility-list { margin-top: var(--s3); }
.sport-visibility-row { cursor: default; }
.sport-visibility-row form { flex: 0 0 auto; margin: 0; }
.sport-visibility-row .btn { min-height: var(--tap); min-width: 102px; }
.sport-visibility-row form.label-form { flex: 1 1 100%; display: flex; gap: 8px; align-items: flex-start; margin: 10px 0 2px; }
.sport-visibility-row form.label-form label.f { flex: 1 1 auto; margin: 0; }
.sport-visibility-row form.label-form .btn { flex: 0 0 auto; min-width: 92px; }
.sport-visibility-row form.label-form .hint { font-size: 13px; margin-top: 5px; }
/* v1.75.0 phone layout (<=640px). Before: the alias input was squeezed to ~58px between the
   avatar, the save button and the 숨기기 button. Now one grid per row, the wrappers
   (.grow, the alias form, its label) flatten with display:contents so their children
   place directly: top line avatar / name / status / compact toggle, then the alias input
   across the row with 별명 저장, then the help text across the row. Desktop is untouched. */
@media (max-width: 640px) {
  .sport-visibility-row[data-sv-row] {
    display: grid; grid-template-columns: 46px minmax(0, 1fr) auto; column-gap: 10px; row-gap: 4px;
    grid-template-areas: "av t tg" "av s tg" "l l l" "in in sv" "h h h"; align-items: center;
  }
  .sport-visibility-row[data-sv-row] > * { min-width: 0; }
  .sport-visibility-row[data-sv-row] .grow,
  .sport-visibility-row[data-sv-row] form.label-form,
  .sport-visibility-row[data-sv-row] form.label-form label.f { display: contents; }
  .sport-visibility-row[data-sv-row] > .avatar { grid-area: av; align-self: start; }
  .sport-visibility-row[data-sv-row] .t { grid-area: t; }
  .sport-visibility-row[data-sv-row] .s { grid-area: s; margin-top: 0; }
  .sport-visibility-row[data-sv-row] > form:last-child { grid-area: tg; align-self: start; justify-self: end; }
  .sport-visibility-row[data-sv-row] > form:last-child .btn { min-width: 0; padding: 0 14px; }
  .sport-visibility-row[data-sv-row] form.label-form .l { grid-area: l; margin: 8px 0 0; }
  .sport-visibility-row[data-sv-row] form.label-form input[type=text] { grid-area: in; min-width: 0; width: 100%; }
  .sport-visibility-row[data-sv-row] form.label-form .btn { grid-area: sv; min-width: 0; padding: 0 14px; align-self: stretch; }
  .sport-visibility-row[data-sv-row] form.label-form .hint { grid-area: h; margin-top: 2px; }
}

/* Kakao Login uses the official yellow and readable dark text. */
.kakao-action { background: #fee500; color: #191919; border: 1px solid #e4ce00; }
.kakao-action:hover, .kakao-action:active { background: #f4dc00; }
.auth-divider { display: flex; align-items: center; gap: 12px; margin: 20px 0; color: var(--ink-2); font-size: var(--fs-sm); font-weight: 700; }
.auth-divider::before, .auth-divider::after { content: ""; height: 1px; background: var(--line); flex: 1; }
.kakao-setup-card code { display: block; margin: 8px 0; padding: 10px; border: 1px solid var(--line); background: var(--card-2); border-radius: var(--r-sm); font-family: monospace; font-size: 14px; word-break: break-all; }
.kakao-setup-card .btn { margin-top: 8px; min-height: var(--tap); }

/* 회원 홈: [새 경기 열기] 자리에 들어가는 한 줄. 버튼처럼 보이면 안 된다(누를 것이 없다). */
.club-only-note {
  margin-top: 22px; border: 1.5px dashed var(--ink-3); border-radius: var(--r);
  background: var(--card-2); color: var(--ink-2);
  font-size: var(--fs-base); font-weight: 700; line-height: 1.55; text-align: center;
  padding: 18px 14px; word-break: keep-all;
}
.club-only-note b { color: var(--ink); font-weight: 800; }

/* ------------------------------------------------------------------ 휴회 (v1.13.0)
 *
 * 휴회 회원에게 "왜 안 되는지" 를 적는 칸이다. 버튼처럼 보이면 안 된다(누를 것이 없다).
 * 경고(빨강)로 칠하지도 않는다. 잘못한 것이 아니라 쉬고 있는 상태이고,
 * 이 클럽 회원 대부분이 어르신이라 빨간 화면은 사고로 읽힌다.
 */
.rest-note {
  border: 1.5px solid var(--hold); border-radius: var(--r); background: var(--hold-bg);
  color: var(--ink); font-size: var(--fs-base); font-weight: 700; line-height: 1.6;
  padding: 16px 15px; word-break: keep-all;
}
.rest-note b { font-weight: 800; }
.rest-note .sm { display: inline-block; margin-top: 6px; font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2); }
/* 하단 고정 자리에서 [신청하기] 대신 들어가는 한 줄. 버튼 높이를 그대로 차지한다. */
.sticky-cta .cta-note {
  width: 100%; min-height: var(--btn-h); display: flex; align-items: center; justify-content: center;
  border: 1.5px dashed var(--hold); border-radius: var(--r); background: var(--hold-bg);
  color: var(--hold); font-size: var(--fs-base); font-weight: 800; text-align: center; padding: 10px 12px;
  word-break: keep-all;
}

/* 운영자 회원 화면: 상태 세 가지가 각각 무엇을 하는지. 라벨과 동작이 어긋나지 않게 늘 함께 보인다. */
.status-guide { display: grid; gap: 8px; margin-bottom: 18px; }
.status-guide .row {
  border: 1.5px solid var(--line); border-radius: var(--r-sm); background: var(--card-2); padding: 11px 12px;
}
.status-guide .row.now { border-color: var(--turf); background: var(--turf-bg); }
.status-guide .k { display: block; font-size: var(--fs-base); font-weight: 800; margin-bottom: 3px; }
.status-guide .row.now .k { color: var(--turf-dark); }
.status-guide .tag {
  font-size: var(--fs-xs); font-weight: 800; color: #fff; background: var(--turf);
  border-radius: var(--r-sm); padding: 3px 8px; vertical-align: middle;
}
.status-guide .v { display: block; font-size: var(--fs-base); font-weight: 600; line-height: 1.55; color: var(--ink-2); word-break: keep-all; }

/* ------------------------------------------------------------------ 클럽 소개 (v1.16.0)
 *
 * 고객 말 그대로: "회사 소개하는 칸도 있으면 합니다".
 * 대표님이 `/admin/club-intro` 에서 직접 쓰는 글이 여기 나온다. 비면 칸 자체가 없다.
 *
 * 읽는 글이라 본문(17px)보다 조금 크고(17.5px) 줄 간격을 넉넉히 준다. 광고 상자가
 * 아니므로 테두리를 세게 두르지 않는다 — 흰 카드 한 장에 글만 있는 편이 읽기 쉽다.
 * `word-break: keep-all` 은 한글 단어가 줄 끝에서 잘리지 않게 한다(360px 에서 특히).
 */
.clubintro {
  margin-top: var(--s6);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  padding: var(--s5) var(--s4);
}
.clubintro h2 {
  font-size: var(--fs-lg); font-weight: 800; margin: 0 0 var(--s3); line-height: var(--lh-tight);
  border-left: 4px solid var(--ink); padding-left: 10px;
}
.clubintro p {
  font-size: var(--fs-md); line-height: 1.75; color: var(--ink); margin: 0 0 11px;
  word-break: keep-all; white-space: normal;
}
.clubintro p:last-child { margin-bottom: 0; }

/* ------------------------------------------------- 긴 글 접기 `.fold` (v1.51.0)
 *
 * 대표님 말 그대로: 「소개글이 너무많아 길어지는데 한번 눌러주면 나오게끔 하는게
 * 좋을듯합니다」 → 이어서 「긴거는 그렇게 해주시면감사」.
 * 2026-09-15 에 677자(48줄)짜리 프로그램 안내를 저장하셨고, 그 한 칸이 390px 에서
 * 1,300px 을 넘게 먹는다. v1.50.0 이 홈을 263px 줄여 놓은 것이 도로 들어간다.
 *
 * **소개 글 전용 장치가 아니다.** 「긴거는」 이라고 하셨으므로 회원이 쓴 긴 글이 나오는
 * 칸은 전부 같은 부품을 쓴다(클럽 소개 · 공지 본문 · 구장 안내/편의시설 · 대회 안내).
 * 새 칸이 생기면 `views/partials/fold.ejs` 를 부르면 그만이고, 여기 CSS 는 안 는다.
 * **칸마다 따로 베껴 만들지 말 것** - 한쪽만 고쳐지는 날이 온다.
 *
 * ### 접는 장치가 체크상자인 이유
 *
 * **자바스크립트를 한 줄도 쓰지 않는다.** 숨은 체크상자 하나와 형제 선택자(~)뿐이라
 * 스크립트가 막히는 카카오톡·네이버 인앱 브라우저에서도 그냥 열린다. 이 파일(CSS)을
 * 못 불러오는 상황에서는 `max-height` 도 같이 없으므로 **글이 전부 보인다**.
 * 어떤 실패에서도 글이 숨겨진 채 잠기지 않는다 - 그게 이 구조를 고른 이유다.
 * 펴고 접는 데 화면이 다시 뜨지 않으므로 보던 자리도 튀지 않는다.
 *
 * `details`/`summary` 를 쓰지 않은 이유: 접힌 상태에서 **앞 세 줄을 보여 줘야** 하는데
 * summary 안에 본문 일부를 넣으면 같은 글을 두 벌 찍어야 하고, 그 순간 자른 조각이
 * 생긴다. 지금은 본문이 한 벌이고 화면이 높이만 가린다.
 *
 * ### 세 줄
 *
 * 접힌 높이 = 본문 글자(--fs-md 18px) × 줄간격 1.75 × 3줄. 글자 크기를 바꾸면 이 값도
 * 저절로 따라온다. **px 로 못 박지 말 것** - 어르신 글씨 크기가 이 저장소의 요구사항이다.
 *
 * 가려지는 것은 `is-folded` 가 붙었을 때뿐이다. 짧은 글에는 서버가 그 클래스도
 * [더보기] 단추도 아예 안 붙인다(눌러도 볼 것이 없는 빈 단추를 만들지 않는다).
 */
.fold-t {
  /* 화면에서 안 보이지만 키보드·보조기기에는 남는다. display:none 으로 지우면
     키보드로 펼칠 방법이 사라진다. */
  position: absolute; width: 1px; height: 1px; margin: 0; padding: 0;
  opacity: 0; overflow: hidden; clip-path: inset(50%);
}
.fold.is-folded .fold-text {
  position: relative; overflow: hidden;
  /* 세 줄 + 네 번째 줄의 절반. **일부러 반 줄을 남긴다** - 딱 세 줄에서 자르면
     "글이 여기서 끝났다" 로 읽히고, 반쯤 걸친 줄이 있어야 아래에 더 있다는 것이
     글자만으로 보인다. [더보기] 단추는 그 사실을 한 번 더 말해 주는 것이다. */
  max-height: calc(var(--fs-md) * 1.75 * 3.5);
}
/* 잘린 자리가 "여기서 끝" 이 아니라 "더 있다" 로 읽히게 아래쪽을 흐린다.
   바탕색은 칸마다 다를 수 있어 `--fold-bg` 로 받는다(기본은 흰 카드). */
.fold.is-folded .fold-text::after {
  /* 흐림은 **얇게**. 세 줄만 보이는 칸에서 2em 짜리 흐림은 마지막 한 줄을 통째로
     지워서 "글씨가 깨졌다" 로 읽힌다(390px 실측으로 확인했다). 세 줄은 다 읽히고
     마지막 줄 끝만 살짝 흐려져야 "아래에 더 있다" 로 읽힌다. */
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1.2em;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, var(--fold-bg, var(--card)) 80%);
  pointer-events: none;
}
.fold .fold-t:checked ~ .fold-text { max-height: none; }
.fold .fold-t:checked ~ .fold-text::after { display: none; }

/* 단추. 어르신 기준이라 한 줄을 통째로 쓰고 높이 56px, 글씨 18px 이다
   (NOTES 2장: 주 버튼 60px · 보조 동작도 손가락으로 눌린다). */
.fold .fold-more {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin-top: var(--s3); min-height: 56px; padding: 0 var(--s4);
  background: var(--card-2); border: 1px solid var(--line); border-radius: var(--r-sm);
  font-size: var(--fs-md); font-weight: 800; color: var(--ink);
  cursor: pointer; user-select: none;
}
.fold .fold-more::after { content: "▾"; font-size: 15px; line-height: 1; }
.fold .fold-more .off { display: none; }
.fold .fold-t:checked ~ .fold-more .on { display: none; }
.fold .fold-t:checked ~ .fold-more .off { display: inline; }
.fold .fold-t:checked ~ .fold-more::after { content: "▴"; }
/* 키보드로 체크상자에 초점이 가면 단추에 테두리가 보여야 한다(상자 자체는 안 보인다). */
.fold .fold-t:focus-visible ~ .fold-more { outline: 3px solid var(--turf); outline-offset: 2px; }

/* `plain` 은 **이미 모양이 정해진 자리**에 접기를 얹을 때 쓴다(구장 편의시설 한 줄,
   대회 안내 카드). 그 칸의 글씨 크기·색을 그대로 물려받고 문단 여백을 없애서,
   접을 만큼 길지 않은 글은 **v1.50.0 과 똑같이** 보인다. 이 성질을 깨지 말 것 -
   짧은 글까지 모양이 바뀌면 고치지 않은 칸을 대표님이 고장으로 읽으신다. */
.fold.plain .fold-text p { margin: 0; font-size: inherit; line-height: inherit; color: inherit; }
.fold.plain .fold-text p + p { margin-top: 6px; }
.fold.plain.is-folded .fold-text { max-height: calc(1.6em * 3.5); }
.fold.plain .fold-more { min-height: 44px; margin-top: var(--s2); font-size: var(--fs-base); }

/* ------------------------------------------------ 구역 통째 접기 `.secfold` (v1.57.0)
 *
 * 대표님이 회원 홈을 보시고 하루에 세 번 같은 말을 하셨다:
 *   「웬만하면 정렬은 가로로 부탁드려요. 너무길어요 ㅠㅠ」
 *   「아님 숨김기능을 하면좋을듯」
 *   「전체적으로 다길어서 ㅠㅠ」
 *
 * **추측하지 않고 재 봤다**(`scripts/staging/measure-home-v1560.sh`, 540x900):
 * 540px 에서 회원 홈 문서가 **7,511px = 화면 8.3개**였고, 그중 주 동작인 **경기 목록이
 * 끝나는 자리가 3,006px(3.3화면)** 였다. 나머지 **약 3,900px(4.3화면)** 이 전부 경기
 * 목록 **아래**의 안내 칸들이다(포스터 · 공지 · 쿠폰 · 소개 · 가맹점 · 후원사 · 서류).
 *
 * 그래서 그 묶음을 **하나로 접는다.** 접힌 상태에서 남는 것은 머리줄(60px) 하나다.
 *
 * ### 위의 `.fold`(v1.51.0)와 무엇이 다른가
 *
 * `.fold` 는 **글 한 덩어리**를 세 줄만 남기고 자른다(`max-height`). 여기는 **서로 다른
 * 구역 여럿**이라 자를 "세 줄" 이 없다. 그래서 통째로 접는다. 두 부품을 합치지 말 것 -
 * 긴 글에 이걸 쓰면 세 줄 미리보기가 사라지고, 구역 묶음에 저걸 쓰면 잘린 자리가 이상해진다.
 *
 * ### 자바스크립트가 없다 (`.fold` 와 같은 이유 · 같은 수법)
 *
 * 숨은 체크상자 + 형제 선택자뿐이다. 스크립트가 막히는 **카카오톡 인앱 브라우저**에서도
 * 그냥 열린다. 이 CSS 를 못 불러오면 `visibility`/`max-height` 가 없으므로 **안이 전부
 * 보인다** - 어떤 실패에서도 내용이 잠기지 않는다. 여는 동작을 스크립트로 바꾸지 말 것.
 *
 * ### `display:none` 을 쓰지 않은 이유 ★이게 이 절에서 제일 중요하다★
 *
 * 안에 **후원사 · 가맹점 · 포스터 돌아가는 상자**(`public/js/rotator.js`)가 들어 있다.
 * 그 스크립트는 기동할 때 `getBoundingClientRect().width` 로 **한 판에 몇 칸이 서는지**를
 * 재고, 그 값으로 `[이전]/[다음]` 줄을 켠다(`paint()` 의 `nav.hidden = pages <= 1`).
 *
 * `display:none` 이면 그 순간 **폭이 0** 이라 "한 판에 다 들어간다" 로 계산되고,
 * 대표님이 접기를 펼쳐도 **화면을 돌리기 전까지 [이전]/[다음] 이 영영 안 나온다.**
 * `visibility:hidden` 은 **자리(폭)를 그대로 두고** 안 보이게만 하므로 그 계산이 맞는다.
 * (높이는 0 이라 `IntersectionObserver` 는 "화면 밖" 으로 보아 자동 넘김도 안 돈다 -
 * 접혀 있는 동안 안 도는 것이 맞다. 펼치면 그때부터 돈다.)
 */
.secfold { margin-top: var(--s5); }
.secfold-t {
  /* `.fold-t` 와 같은 규칙: 화면에는 안 보이지만 키보드·보조기기에는 남는다. */
  position: absolute; width: 1px; height: 1px; margin: 0; padding: 0;
  opacity: 0; overflow: hidden; clip-path: inset(50%);
}
/* 접힌 상태. 자리는 지키고(폭) 높이만 0 으로 눌러 아래 내용을 위로 끌어올린다. */
.secfold .secfold-body { visibility: hidden; max-height: 0; overflow: hidden; }
.secfold .secfold-t:checked ~ .secfold-body { visibility: visible; max-height: none; }
/* 머리줄. 어르신 기준이라 높이 60px · 글씨 18px 이다(주 버튼과 같은 크기). */
.secfold-head {
  display: flex; align-items: center; gap: var(--s3);
  min-height: 60px; padding: 10px var(--s4);
  background: var(--card-2); border: 1px solid var(--line); border-radius: var(--r-sm);
  color: var(--ink); cursor: pointer; user-select: none;
}
.secfold-head .grow { flex: 1; min-width: 0; }
.secfold-head .t { display: block; font-size: var(--fs-md); font-weight: 800; }
.secfold-head .s { display: block; margin-top: 2px; font-size: var(--fs-sm); font-weight: 600; color: var(--muted); }
.secfold-head::after { content: "▾"; font-size: 15px; line-height: 1; }
.secfold .secfold-t:checked ~ .secfold-head::after { content: "▴"; }
/* 상태를 글자로도 말한다. 화살표만으로는 어르신 화면에서 뜻이 안 통한다(NOTES 2장). */
.secfold-head .now { font-size: var(--fs-sm); font-weight: 800; color: var(--turf); white-space: nowrap; }
.secfold-head .now .on { display: inline; }
.secfold-head .now .off { display: none; }
.secfold .secfold-t:checked ~ .secfold-head .now .on { display: none; }
.secfold .secfold-t:checked ~ .secfold-head .now .off { display: inline; }
.secfold .secfold-t:focus-visible ~ .secfold-head { outline: 3px solid var(--turf); outline-offset: 2px; }

/* ------------------------------------------------ 목록 접기 `.listfold` (v1.77.4)
 * 대표님 「근데 예약보이는게 너무 길어서. ㅠㅠ」. Used by the home 모집중인 경기 list, each
 * sport on /status, and each 자리표 table on /status (`.brdfold`). Same rules as `.secfold`:
 * checkbox + sibling selector (no JS), and visibility/max-height instead of display:none so
 * the /status 20s updater and anything measuring width still work. Without this CSS the
 * body is simply visible. Lives in app.css because matches.ejs does not load home.css.
 */
.listfold-t {
  position: absolute; width: 1px; height: 1px; margin: 0; padding: 0;
  opacity: 0; overflow: hidden; clip-path: inset(50%);
}
.listfold-body { visibility: hidden; max-height: 0; overflow: hidden; }
.listfold-t:checked ~ .listfold-body { visibility: visible; max-height: none; }
.listfold-head {
  display: flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: 52px; margin: 10px 0; padding: 8px var(--s4);
  background: var(--card-2); border: 1px solid var(--line); border-radius: var(--r-sm);
  color: var(--turf-dark); font-size: var(--fs-base); font-weight: 800;
  cursor: pointer; user-select: none;
}
.listfold-head::after { content: "▾"; font-size: 15px; line-height: 1; }
.listfold-t:checked ~ .listfold-head::after { content: "▴"; }
.listfold-head .now .off { display: none; }
.listfold-t:checked ~ .listfold-head .now .on { display: none; }
.listfold-t:checked ~ .listfold-head .now .off { display: inline; }
.listfold-t:focus-visible ~ .listfold-head { outline: 3px solid var(--turf); outline-offset: 2px; }
/* 자리표 head on /status: one tappable line inside the row. */
.brdfold > .brdfold-head { display: flex; align-items: center; gap: 8px; min-height: 40px; margin-bottom: 0; cursor: pointer; user-select: none; }
.brdfold > .brdfold-head .grow { flex: 1; min-width: 0; }
.brdfold > .brdfold-head .now { color: var(--turf-dark); font-weight: 800; white-space: nowrap; }
.brdfold > .brdfold-head .now .off { display: none; }
.brdfold > .brdfold-head::after { content: "▾"; font-size: 13px; }
.brdfold > .listfold-t:checked ~ .brdfold-head .now .on { display: none; }
.brdfold > .listfold-t:checked ~ .brdfold-head .now .off { display: inline; }
.brdfold > .listfold-t:checked ~ .brdfold-head::after { content: "▴"; }
.brdfold > .listfold-t:checked ~ .listfold-body { margin-top: 6px; }

/* ------------------------------------------------------------------ 아래 기관 띠 (v1.16.0 → v1.17.0)
 *
 * 고객 말 그대로: "젤밑에는 문체부.대한체육회.대구시체육회.달성군체육회 로고랑 같이".
 *
 * **v1.17.0 에서 이 띠가 화면 맨 아래 숯색 푸터 안으로 들어갔다.** 대한체육회 화면에서
 * 실제로 가져온 것이 이 한 가지다: 소속·유관기관 표시가 진한 바탕의 마지막 띠에 모여
 * 있고, 그 옆에 기관 이름과 소재지가 적혀 있다. 회원 앱과 소개 홈페이지가 **같은 푸터**를
 * 쓰기 때문에 두 화면이 한 기관의 것으로 읽힌다.
 *
 * 다만 그 사이트의 푸터 실수는 따라 하지 않았다. 거기 소셜 아이콘은 #28303A 바탕에
 * #28303A 로 칠해져 있어 **대비 1:1**, 사실상 보이지 않는다. 여기 글씨는 전부
 * --bar-ink(9.87:1) 이상이고, 기관 이름은 흰색(15.61:1)이다.
 *
 * 로고 띠 자체의 규칙은 v1.16.0 그대로다(바꾸지 말 것):
 *   - **돌지 않는다.** 소속·지정 표시라 전부 한 번에 보여야 한다.
 *   - 칸을 **가운데로 모은다**. 로고가 하나뿐일 때 "여섯 칸 중 하나만 찬 줄" 이 아니라
 *     가운데 놓인 한 칸으로 보여야 한다.
 *   - 한 칸 폭이 **144px 고정**이다. 좁으면 칸을 줄이는 대신 **줄을 바꾼다**.
 *     `1fr` 격자로 바꾸지 말 것 — 한 개일 때 로고가 화면 폭만큼 늘어난다.
 *   - 기관 이름은 그림이 안 떴을 때의 유일한 설명이라 반드시 글자로 남는다.
 */

/* 화면 폭을 꽉 채우는 띠다. `.wrap` 의 좌우 여백을 **정확히 그만큼** 되민다.
   --s4(16px) 로 되밀던 때는 여백(14px)보다 2px 씩 넓어서 360px 에서 가로 스크롤이 생겼다.
   숫자를 여기 직접 적지 말 것. */
.sitefoot {
  background: var(--bar); color: #fff;
  margin: var(--s8) calc(-1 * var(--wrap-pad)) calc(-1 * (var(--tab-h) + 28px + env(safe-area-inset-bottom)));
  padding: var(--s7) var(--s4) calc(var(--tab-h) + var(--s7) + env(safe-area-inset-bottom));
}
/* `.flat` = **본문 상자(.wrap) 밖에 홀로 서는 푸터**다. 소개 홈페이지가 그렇다.
   그 화면에는 되밀 여백도 없고(이미 화면 폭을 다 쓴다) 비워 줄 하단 탭도 없다.
   좌우 음수 여백을 그대로 두면 그만큼 화면이 가로로 밀린다(360px 에서 374px 이 되어
   실제로 가로 스크롤이 생겼다). 셋을 함께 되돌린다. */
.sitefoot.flat { margin-left: 0; margin-right: 0; margin-bottom: 0; padding-bottom: var(--s7); }
.sitefoot .in { max-width: var(--app-max); margin: 0 auto; }
.sitefoot .org { font-size: var(--fs-lg); font-weight: 800; line-height: var(--lh-tight); margin: 0 0 var(--s2); }
.sitefoot .ln { font-size: var(--fs-base); line-height: 1.6; color: var(--bar-ink); margin: 0 0 var(--s1); word-break: keep-all; }
.sitefoot .ln a { color: #fff; text-decoration: underline; text-underline-offset: 4px; }
/* 전화번호는 누르면 걸린다. 누르는 것이므로 48px 이다. */
.sitefoot .tel {
  display: inline-flex; align-items: center; gap: var(--s2); min-height: var(--tap);
  font-size: var(--fs-md); font-weight: 800; color: #fff;
  text-decoration: underline; text-underline-offset: 4px;
}
.sitefoot .tel .i { width: 20px; height: 20px; }

/* ------------------------------------------------------------------ 사업자 정보 (v1.21.0)
 *
 * 대표님이 데스크톱에서 화면을 열어 보시고 보내신 첫 그림이 참고 사이트의 **푸터**였다.
 * 그래서 클럽이 직접 공시한 여덟 줄을 여기 세웠다. 값은 `src/lib/config.ts` 의 CLUB 이고
 * 회원 앱과 소개 홈페이지가 같은 푸터를 쓰므로 두 화면에 같이 나온다.
 *
 * 규칙 셋:
 *   1. **라벨과 값이 짝으로 읽혀야 한다.** 그래서 dl/dt/dd 다. "314-82-18180" 이라는
 *      숫자 하나만 떠 있으면 그게 사업자번호인지 전화인지 알 수 없다.
 *   2. 글씨는 --fs-sm(15px) 까지만 내려간다. 14px 이 이 제품의 절대 바닥이고, 참고
 *      사이트의 12px 짜리 푸터를 따라가지 않는다.
 *   3. 색은 --bar-ink(숯색 위 9.87:1)다. 흐린 회색으로 낮추지 말 것 — 참고 사이트의
 *      푸터가 정확히 그렇게 해서 대비 1:1 인 글자를 만들어 놓았다.
 *
 * 좁은 화면에서는 라벨이 값 위에 얹히지 않고 **한 줄에 라벨: 값**으로 흐른다.
 * 라벨 폭을 고정하면 "개인정보보호책임자" 같은 긴 라벨 때문에 값 자리가 사라진다.
 */
.sitefoot .foot-top { display: grid; gap: var(--s5); }
.sitefoot .foot-id .ln { margin-bottom: var(--s2); }
.sitefoot .bizinfo { margin: 0; padding: 0; }
.sitefoot .bizinfo .bi {
  display: flex; flex-wrap: wrap; gap: 0 var(--s2);
  font-size: var(--fs-sm); line-height: 1.7; color: var(--bar-ink);
}
.sitefoot .bizinfo dt { font-weight: 700; color: var(--bar-ink); }
.sitefoot .bizinfo dt::after { content: " :"; }
.sitefoot .bizinfo dd { margin: 0; word-break: keep-all; }
.sitefoot .bizinfo dd a { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

/* ------------------------------------------------------- 관련사이트 (v1.60.0)
 *
 * 참고 사이트(sportsclub.sports.or.kr) 푸터의 마지막 줄을 늦게 가져온 것이다. 주소와
 * "왜 이 다섯 줄인가" 는 `src/lib/officialsite.ts` 의 `RELATED_SITES` 에 있다.
 *
 * 규칙:
 *   1. **줄마다 48px**. 전부 누르는 것이고, 푸터라고 해서 작아지지 않는다
 *      (`scripts/ui-gate.js` 가 360~1920 전 폭에서 이걸 잰다).
 *   2. 이름은 흰색(15.61:1), 설명은 --bar-ink(9.87:1). 참고 사이트의 회색-위-회색을
 *      따라가지 않는다.
 *   3. 좁으면 한 칸, 640 부터 두 칸, 1024 부터 세 칸. `auto-fit` 을 쓰지 않는 이유는
 *      후원사 띠와 같다 - 줄이 하나일 때 칸 하나가 푸터 폭만큼 늘어난다.
 *   4. 알약 모양·테두리 상자로 만들지 않는다. 푸터에 카드가 들어가면 사업자 정보
 *      블록과 형제가 아니라 그 안에 든 것처럼 읽힌다(카드 속 카드 금지).
 */
.sitefoot .relsites { margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--bar-line); }
.sitefoot .relsites h2 {
  font-size: var(--fs-sm); font-weight: 800; color: var(--bar-ink);
  letter-spacing: 0.01em; margin: 0 0 var(--s2);
}
.sitefoot .relsites ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0 var(--s5); }
.sitefoot .relsites a {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 var(--s2);
  min-height: var(--tap); padding: var(--s2) 0;
  color: #fff; text-decoration: none;
}
.sitefoot .relsites a .t {
  font-size: var(--fs-base); font-weight: 700;
  text-decoration: underline; text-underline-offset: 4px;
}
.sitefoot .relsites a .s { font-size: var(--fs-sm); color: var(--bar-ink); word-break: keep-all; }
@media (min-width: 640px)  { .sitefoot .relsites ul { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1024px) { .sitefoot .relsites ul { grid-template-columns: 1fr 1fr 1fr; } }

.sitefoot .legal {
  margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--bar-line);
  font-size: var(--fs-sm); color: var(--bar-ink); line-height: 1.6;
}
.sitefoot .legal a { color: #fff; text-decoration: underline; text-underline-offset: 4px; }
/* 개인정보처리방침 링크도 누르는 것이다. */
.sitefoot .legal .lk { display: inline-flex; align-items: center; min-height: var(--tap); padding-right: var(--s3); }

/* ------------------------------------------------------------- 협력 기관 띠 (v1.16.0)
 *                                        v1.22.0 에서 **돌아가는 상자**가 되었고
 *                                        푸터 맨 위(사업자 정보 위)로 올라갔다.
 *
 * 대표님 말 그대로: "협력기관도 돌아가게끔 부탁드려요 바로위에랑 자리 변경도 부탁드려요".
 *
 * 회전 장치는 **후원사와 같은 하나**다(.rotbox + public/js/rotator.js). 이 아래 규칙은
 * 그 상자를 숯색 푸터 위에서 읽히게 만드는 것뿐이고, 새 회전 코드가 아니다.
 * `.rotbox` 규칙(위쪽 767줄 근처)보다 **뒤에 있어야** 색 덮어쓰기가 먹는다. 옮기지 말 것.
 *
 * 자리가 위로 올라갔으므로 경계선도 위가 아니라 **아래**다. 푸터의 첫 요소가 윗줄을
 * 달고 있으면 그 줄이 푸터 바깥의 본문과 붙어 보인다. */
.afflstrip { margin-top: 0; margin-bottom: var(--s6); padding-bottom: var(--s5); border-bottom: 1px solid var(--bar-line); }
/* ---------------------------------------------------------------------------
 * v1.62.0 — 이 띠는 **후원사 띠와 같은 상자·같은 칸**이다 (대표님이 그렇게 정하셨다)
 * ---------------------------------------------------------------------------
 *
 * 대표님이 그대로 적어 보내신 말(2026-09-22):
 *   「클럽.협력기관도 후원사처럼 부탁드려요. 많이나오게 보이는화면이 6개나 8개.
 *    나오면 눌렀을때. 스폰서랑 똑같게 부탁드려요」
 *
 * 그래서 이 절은 위 `.sponsors` 절과 **같은 값**을 쓴다(한 판 = 가로 3칸 x 2줄 = 6칸,
 * 세로 타일, 로고 위 · 이름 아래, 주소가 있으면 「홈페이지」 한 줄). 새 규칙을 지어내지
 * 말고 그쪽을 먼저 읽을 것 - 두 띠의 값이 어긋나는 순간 「스폰서랑 똑같게」 가 깨진다.
 *
 * ★`--per: 3` 을 **모든 폭에서** 적는다★ - `.rotbox.multi` 의 기본 사다리는 폰 1 /
 * 768 2 / 1024 3 이라, 여기서 한 단계만 적으면 폰에서 한 판이 1 x 2 = 두 칸이 된다.
 * 대표님은 「보이는화면이 6개나 8개」라고 숫자로 말씀하셨다. (`.rotbox.multi` 쪽 값은
 * 건드리지 않는다 - 그 클래스는 소개 홈페이지의 다른 상자도 같이 쓰고, v1.31.0 에
 * 4칸으로 올렸다가 되돌린 자리다.)
 *
 * 줄 수는 markup 의 `data-rot-rows="2"` 가 알려 준다(views/partials/affiliations.ejs). */
.rotbox.afflstrip { --per: 3; --slide-gap: 8px; }
@media (min-width: 768px) { .rotbox.afflstrip { --per: 3; --slide-gap: 12px; } }
/* 제목도 후원사와 같은 `.cap`(왼쪽 세로선 + 옆에 붙는 한 줄 설명)이다. v1.18.0 의
   가운데 정렬 제목(`.afflstrip > h2`)은 markup 이 h2 를 `.cap` 안으로 넣으면서 죽었고,
   그것이 「스폰서랑 똑같게」 의 뜻이다. 여기서 바꾸는 것은 **색뿐**이다 - 이 띠만 숯색
   푸터 위에 서 있어서 기본값(세로선 --ink, 설명 --ink-2 = 숯색 위 1.8:1)이 안 보인다.
   흰 제목 15.61:1 · 설명 --bar-ink 9.87:1. 크기·정렬·굵기는 후원사 그대로 둘 것
   (768/1024 에서 커지는 사다리는 `.rotbox > .cap h2` 한 곳에 있다). */
.afflstrip > .cap h2 { color: #fff; border-left-color: var(--bar-ink); }
.afflstrip > .cap .sub { color: var(--bar-ink); }
/* 한 판 6칸을 만드는 두 줄 격자. 후원사·가맹점 띠와 **같은 방법**이고 이유도 같다:
   칸이 「위 · 아래 → 다음 열」 순서로 채워지므로 한 판의 가로 이동 거리가 그대로
   `칸 폭 x 칸 수` 여서 rotator.js 를 한 줄도 고치지 않는다(긴 설명은 `.merchants
   .rot-track` 주석에 있다).
   ★칸 폭을 쥐는 것은 **열**(`grid-auto-columns`)이다★ - `.rot-slide` 의 백분율 폭이
   아니다. 격자 칸에서 백분율 폭은 자기 열을 기준으로 풀리는데 열이 `auto` 면 서로가
   서로를 기다리는 순환이 되어 칸이 내용 폭까지 쪼그라든다(가맹점 띠에서 실측했다).
   **되돌리지 말 것.**
   `.rotbox .rot-track { display: flex }` 를 덮어쓰는 규칙이라 특이도가 같다. 이 절이
   그 기본값보다 **뒤에 있어야** 먹는다. 옮기지 말 것. */
.afflstrip .rot-track {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, auto);
  grid-auto-columns: calc(100% / var(--per));
  align-items: stretch;
  row-gap: 8px;
}
/* 위·아래 두 칸의 높이를 맞춘다(기관 이름이 한 줄이든 두 줄이든 줄이 들쭉날쭉하지 않다).
   폭은 열이 쥐고 있으므로 칸은 `auto` 로 풀어 준다. */
.afflstrip .rot-slide { display: flex; width: auto; }
.afflstrip .rot-slide > .sponsor { width: 100%; }
/* [이전] [n / 5] [다음] 은 후원사와 같은 버튼이다(흰 바탕 · 48px). 가운데 숫자만
   숯색 위에서 읽히게 색을 바꾼다 — 기본값 --ink-2(#454b53)는 숯색 위에서 1.8:1 이다.
   --bar-ink 는 9.87:1, 굵은 현재 번호는 흰색 15.61:1 이다. */
.afflstrip .rot-count { color: var(--bar-ink); }
.afflstrip .rot-count b { color: #fff; }
/* ---------------------------------------------------------------------------
 * 칸 모양: **후원사 칸 그대로** (v1.62.0)
 * ---------------------------------------------------------------------------
 *
 * markup 의 칸 클래스가 `sponsor affl` 두 개다. 그래서 흰 카드·테두리·글씨 크기·누름
 * 반응은 위 `.sponsor` 기본값에서 **그냥 물려받는다.** 여기 적는 것은 `.sponsors` 절이
 * 자기 띠에만 걸어 둔 「세로 타일」 값을 이 띠에도 같이 거는 일뿐이다(`.sponsors` 는
 * 그 조상 클래스가 있어야 먹으므로 여기까지 오지 않는다).
 *
 * 값은 `.sponsors .sponsor` 와 **글자 하나까지 같게** 유지할 것. 한쪽만 고치면 그
 * 순간 「스폰서랑 똑같게」 가 깨진다.
 *
 * ### v1.32.0 「지정서랑 같은 사이즈로」 · v1.50.0 「사이즈 똑같게」 는 어떻게 되었나
 *
 * v1.32.0 의 `aspect-ratio: var(--affl-ar)`(서류와 같은 비율을 따라가던 칸)은
 * v1.62.0 에서 **지웠다.** 한 판이 6칸이 되면서 칸 폭이 서류의 1/3 이라, 서류 비율을
 * 씌우면 칸이 그대로 1/3 높이로 눕는다. 대표님의 나중 말씀(「스폰서랑 똑같게」)이
 * 앞 말씀을 덮는다. `--affl-ar` 을 다시 적지 말 것 - 서버도 그 값을 더 넘기지 않는다.
 *
 * v1.50.0 의 「로고 열 장이 전부 같은 크기」는 **그대로 살아 있다.** 그 성질은 비율이
 * 아니라 (가) 칸이 정사각이고 (나) `img` 가 칸과 같은 크기이며 `contain` 이라는
 * 두 가지에서 나오고, 둘 다 아래에 그대로 있다. 게다가 업로드 때 `squarePad` 가 모든
 * 로고를 480x480 흰 여백 정사각으로 저장하므로(src/lib/sponsors.ts) 원본 비율이
 * 0.47~1.33 로 벌어져 있어도 그려지는 크기는 같다.
 */
/* 후원사와 같은 세로 타일. 로고 위 · 이름 아래 · 가운데 정렬. */
.afflstrip .sponsor {
  flex-direction: column; justify-content: flex-start; gap: 7px;
  padding: 10px 8px; min-height: 124px; text-align: center;
}
/* 주소가 있는 칸은 누르는 것이다. 어르신 화면에서 누름 자리는 48px 아래로 내려가지
   않는다(tokens.css 의 --tap). 후원사 칸과 같은 줄이다. */
.afflstrip a.sponsor { min-height: max(124px, var(--tap)); }
/* 로고 칸 크기. 물려받은 값은 96px 짜리 가로형 카드용이라 6칸 타일에서는 넘친다.
   후원사 타일과 **같은 72px**, 768px 이상에서는 서류와 같은 `--doc-pic` 를 읽는다
   (「인정서는 후원사 사이즈로 통일」. 그 변수는 :root 에 있고 768px 120px ·
   1600px 200px 로 이미 단이 잡혀 있다. 여기에 숫자를 새로 적지 말 것). */
.afflstrip .affl .logo { width: 72px; height: 72px; }
@media (min-width: 768px) {
  .afflstrip .affl .logo { width: var(--doc-pic, 72px); height: var(--doc-pic, 72px); }
}
/* 흰 로고 상자는 **흰색으로 남긴다** - 숯색 위에 기관 표장을 바로 올리면 표장마다
   대비가 제각각이 된다(그 사이트가 정확히 그래서 아이콘이 안 보인다). */
/* ---------------------------------------------------------------------------
 * v1.50.0 — 대표님: **「젤 밑에 협력협체도 사이즈 똑같게 해주세여」**
 * ---------------------------------------------------------------------------
 *
 * v1.32.0 이 칸 크기는 맞췄지만 **그려진 로고 크기는 제각각**으로 남아 있었다.
 * 그때 규칙이 `height: 100%; width: auto; max-width: 100%` 였기 때문이다. 이 클럽이
 * 올리신 로고 열 장의 실제 비율은 0.47 ~ 1.33 로 2.8배까지 벌어져 있다
 * (224x480 남구체육회 … 480x360 지정스포츠). 정사각 칸에서 그 규칙이 그리는 그림은:
 *
 *     224x480(0.47)  →  높이 100%, 폭 47%    ← 가늘고 길다
 *     480x480(1.00)  →  칸을 꽉 채운다
 *     480x360(1.33)  →  폭 100%, 높이 75%    ← 낮고 넓다
 *
 * 즉 **로고마다 그려진 크기가 다르다.** 대표님이 보시는 것이 이것이다.
 *
 * v1.29.0 에서 인증서 띠에 같은 일을 한 번 겪었다. 그때는 **상자만** 같게 만들고
 * 안의 그림은 그대로 두었고, 대표님이 「그대로인데요」 라고 다시 말씀하셨다.
 * 그 실수를 반복하지 않는다: 여기서는 **`img` 요소 자체가 칸과 같은 크기**이고
 * (`width/height: 100%`), 어느 로고든 **긴 변이 칸에 정확히 닿는다**(`contain`).
 * 그려진 크기가 같아지는 것이지 상자만 같아지는 것이 아니다.
 *
 * **늘리거나 자르지 않는다.** `contain` 이라 비율은 그대로고(`cover` 로 바꾸면 관공서
 * 표장이 잘린다, 그건 그 기관의 표시 규정을 어기는 일이다), 남는 쪽은 흰 여백이 된다.
 *
 * ### 칸을 **정사각으로 못 박았다** (서류 비율을 더 따라가지 않는다)
 * v1.32.0 은 `aspect-ratio: var(--affl-ar)` 로 **제일 넓은 서류의 비율**을 따라갔다.
 * 지금 운영값은 마침 `1004 / 1004`(정사각, 사업자등록증)이라 보이는 차이가 없지만,
 * 대표님이 가로로 긴 표창장을 한 장 올리시는 순간 칸이 납작해지고 세로로 긴 로고들이
 * 다시 작아진다. **「사이즈 똑같게」 를 바깥 데이터에 맡길 수 없다.** 세로로 긴 로고와
 * 가로로 긴 로고가 섞인 띠에서 둘의 긴 변이 같아지는 칸 모양은 정사각 하나뿐이다.
 * 여기에 `var(--affl-ar)` 을 다시 적지 말 것. (v1.32.0 의 요청 - 로고가 우표만 하지
 * 않고 칸을 꽉 채울 것 - 은 그대로 지켜진다. 칸은 여전히 판 폭을 다 쓴다.)
 */
.afflstrip .affl .mk {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 1 / 1;
  /* 상하좌우 **같은** 여백이라야 안쪽 칸도 정사각이다. 10px 12px 처럼 갈라 적으면
     안쪽이 가로로 긴 칸이 되고 로고마다 긴 변이 다시 어긋난다(v1.29.0 이 놓친 급의 차이). */
  padding: 10px; border-radius: var(--r-sm);
  background: #fff; border: 1px solid var(--bar-line);
  /* 그림이 안 떠도 누름 자리가 48px 아래로 내려가지 않는다(tokens.css 의 --tap). */
  min-height: var(--tap);
}
/* 원본 비율 그대로 상자 안에 들어간다. 가로로 긴 관공서 표장도 잘리지 않고,
   작은 로고를 억지로 늘리지도 않는다. 로고 색은 건드리지 않는다 (기관 표장을 회색으로
   바꾸거나 색을 입히는 것은 그 기관의 표시 규정을 어기는 일이다). */
/* **로고 열 장이 전부 같은 크기로 그려지는 줄** (v1.50.0. 위 주석의 이유).
   `img` 요소가 칸과 같은 크기이고, `contain` 이 비율을 지킨 채 긴 변을 칸에 맞춘다.
   `width: auto` 로 되돌리지 말 것 — 그 순간 세로로 긴 로고는 가늘어지고 가로로 긴
   로고는 낮아져서, 대표님이 「사이즈 똑같게」 라고 하신 그 상태로 정확히 돌아간다.
   `height: auto` 로 되돌리는 것도 안 된다(v1.31.0 의 우표만 한 로고). */
.afflstrip .affl .mk img {
  display: block; margin: auto;
  width: 100%; height: 100%;
  object-fit: contain; object-position: center;
}
/* 글자 칸. 후원사 타일과 같은 값이다(칸 폭을 다 쓰고, 로고가 남긴 높이만 차지한다). */
.afflstrip .sponsor .tx { flex: 0 1 auto; width: 100%; }
/* 기관 이름. v1.62.0 부터 **흰 카드 위**이므로 색이 --bar-ink(숯색용 9.87:1) 에서
   --ink(흰 바탕 16.9:1) 로 바뀌었다. 숯색 값을 되돌리지 말 것 - 흰 카드 위에서
   1.4:1 이라 사실상 안 보인다.
   이름이 긴 기관(「사회적협동조합 드림즈」)이 있어서 두 줄까지 보이고 그 뒤는 잘린다.
   후원사 타일과 같은 규칙이다. */
.afflstrip .affl .nm {
  margin-top: 0;
  font-size: var(--fs-sm); font-weight: 800; line-height: 1.3; color: var(--ink);
  word-break: keep-all;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* 「홈페이지」 한 줄. **누를 수 있다는 것을 글자로** 말하는 자리다(폰에는 hover 가 없고,
   이 제품은 아이콘만 있는 버튼을 금지한다). 후원사 칸과 같은 초록 밑줄이다. */
.afflstrip .affl .ln {
  font-size: var(--fs-xs); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.afflstrip .affl .go-tx {
  color: var(--turf-dark); font-weight: 800;
  text-decoration: underline; text-underline-offset: 3px;
}
/* 누를 때. 카드 바탕은 `.sponsor:active`(--card-2) 가 이미 바꾸므로 로고 상자만
   같이 눌린 것으로 보이게 한다. 칸 자체가 `a` 라 `> a` 가 아니다. */
.afflstrip a.affl:active .mk { background: var(--line-2); }
/* 넓은 화면에서 상자 폭을 묶는다. 소개 홈페이지 푸터 안쪽은 1440 에서 1280px 이라
   그대로 두면 [이전]/[다음] 버튼 하나가 600px 짜리 띠가 되어 버튼으로 안 보인다.
   **이 블록은 위의 기본값들보다 뒤에 있어야 한다.** 같은 특이도라 순서가 이긴다.

   v1.26.0 에서 460px → 두 단계로 넓혔다. 460px 은 한 판에 한 기관이 서던 때의 값이고,
   그 폭에 기관 셋을 넣으면 로고 칸이 하나에 150px 밖에 안 되어 관공서 표장의 글자가
   읽히지 않는다(대표님 요청은 "3개씩 나오게" 이지 "3개를 작게" 가 아니다).
   768px 에서 2곳 x 240px, 1024px 이상에서 3곳 x 240px 이 들어가는 폭으로 잡았다.
   숫자를 다시 줄이려면 로고 칸(.affl 240px)부터 같이 보아야 한다. */
@media (min-width: 768px) {
  /* v1.32.0: **칸(rot-view)과 버튼줄(rot-nav)의 폭을 갈랐다.**
   *
   * 「협력기관도 지정서랑 같은 사이즈로」 를 넓은 화면에서 조금이라도 좁히려면 로고 칸이
   * 넓어야 한다(로고 높이는 칸 폭에서 나온다). 그래서 로고 띠는 **푸터 안쪽 폭을 그대로**
   * 쓴다. 버튼줄은 예전 값(620/900)에 그대로 묶어 둔다 — 같이 넓히면 [이전]/[다음] 하나가
   * 500px 짜리 띠가 되어 버튼으로 안 보인다(v1.26.0 주석의 그 이유는 여전히 유효하다). */
  .afflstrip .rot-view { max-width: none; margin-left: auto; margin-right: auto; }
  .afflstrip .rot-nav { max-width: 620px; margin-left: auto; margin-right: auto; }
  /* 한 판에 한두 곳만 서 있으니 자리가 남는다. 데스크톱에서 로고 칸을 키운다
     (대표님이 "컴터로 했을때 크고 작고" 라고 하신 그 폭이다). 폰은 그대로 144/76 이다. */
  /* v1.32.0: 칸을 고정 크기로 묶던 두 줄을 지웠다. 이제 칸은 자기 판(rot-slide)을 꽉
     채우고 높이는 서류와 같은 비율에서 나온다. 여기에 240px/124px 을 다시 적으면
     「지정서랑 같은 사이즈로」 가 그 자리에서 무효가 된다. */
}
@media (min-width: 1024px) {
  .afflstrip .rot-nav { max-width: 900px; }
  /* 기관이 6곳 아래로 줄어 한 판을 못 채우면 남은 칸이 왼쪽에 몰린다. 가운데로 세운다.
     ★`safe` 를 빼지 말 것★ - 칸이 판보다 넓어 넘칠 때 맨 앞 칸이 화면 왼쪽 밖으로
     잘려 나가고, 가로 스크롤로는 그 칸에 다시 닿을 수 없다(후원사 띠와 같은 줄이다). */
  .afflstrip .rot-track { justify-content: safe center; }
}

/* ------------------------------------------------------------------ 협력 기관 관리 (v1.16.0)
 *
 * 운영자 화면(`/admin/affiliations`). 로고 하나 = 카드 한 장이고, 순서는 숫자 칸이
 * 아니라 [위로]/[아래로] 두 버튼이다(어르신 화면에서 숫자로 순서를 매기게 하면
 * "3 과 5 사이" 같은 고칠 수 없는 상태가 생긴다).
 */
.card.affl-row { padding: 12px 13px; }
.card.affl-row .row { display: flex; align-items: center; gap: 13px; }
.card.affl-row .mk {
  flex: 0 0 auto; width: 84px; height: 62px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--card); overflow: hidden;
  /* 회원 화면 띠와 같은 이유로 flex 다(위 .afflstrip .affl .mk 주석 참조). */
  display: flex; align-items: center; justify-content: center; padding: 4px;
}
.card.affl-row .mk img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.card.affl-row .mk .none { font-size: var(--fs-xs); color: var(--ink-3); font-weight: 700; }
.card.affl-row .grow { flex: 1; min-width: 0; }
.card.affl-row .nm { font-size: var(--fs-md); font-weight: 800; line-height: 1.35; word-break: keep-all; }
.card.affl-row .acts { display: flex; gap: var(--s2); margin-top: 11px; flex-wrap: wrap; }
.card.affl-row .acts form { flex: 0 0 auto; margin: 0; }
.card.affl-row .acts .btn { min-height: var(--tap); }
/* 못 누르는 [위로]/[아래로]. 0.4 로 낮추면 글자 대비가 기준 아래로 떨어진다. */
.card.affl-row .acts button[disabled] { opacity: 0.62; }

/* 운영 화면의 인증서 줄 (v1.19.0). 기관 로고 줄을 그대로 쓰되 **미리보기 칸만 세로**다.
   서류는 A4 세로라 84x62 가로 칸에 넣으면 우표만 해져서 무슨 서류인지 구별이 안 된다. */
.card.cert-row .mk { width: 66px; height: 90px; }

/* ------------------------------------------------------------------ 공지사항 (v1.20.0)
 *
 * 고객이 그대로 적어 보낸 말: "공지올리수있게도 부탁합니다".
 *
 * **회원 앱 홈 · 소개 홈페이지 · 공지 전체 화면 셋이 이 한 벌을 같이 쓴다.** 그래서
 * home.css 가 아니라 여기(app.css)에 있다. 소개 홈페이지에서만 제목 줄이 그 화면의
 * 다른 구역과 같은 큰 제목이 되고(.notices.sect), 나머지는 회원 앱의 구역 머리를 쓴다.
 *
 * 규칙 셋 (이 클럽 사용자가 파크골프 어르신이라는 데서 전부 나온다):
 *   1. **읽는 글이라 본문이 18px 이고 줄 간격이 넉넉하다.** 클럽 소개 글과 같은 값이다.
 *      목록을 촘촘하게 만들지 말 것 — 여기서 밀도를 벌어도 얻는 것이 없다.
 *   2. **제목이 본문보다 확실히 크다.** 어느 글의 시작인지 훑어서 잡혀야 한다.
 *   3. 접기/펴기·더보기 토글을 만들지 않는다. 홈에서는 서버가 잘라서 보내고, 전문은
 *      /notices 한 곳에 있다(숨은 동작을 만들지 않는다는 이 제품의 규칙).
 */
.notices { margin-top: var(--s6); }
.pub .notices { margin-top: var(--s7); }
.notice-list { display: grid; gap: var(--s3); }
.notices .notice-list { margin-top: var(--s3); }
.pub .notices .notice-list { margin-top: var(--s4); }

/* ---------------------------------------------------- 공지도 가로로 (v1.64.0)
 *
 * 대표님이 그대로 적어 보내신 말: **「공지상황도 가로로 부탁합니다」**(2026-09-23).
 * 이 목록은 v1.62.0 에 예약하기 바로 위로 옮겨 왔는데, 지금은 **세로로 한 장씩** 쌓여
 * 화면을 먹는다(실측: 540 폰 폭에서 세 장이 778px = 화면 한 장 가까이). 말 끝의 "도" 는
 * v1.60.0 부터 가로로 선 경기 · 장터 카드를 가리킨다. 그러니 폰에서부터 두 장을
 * 나란히 세우는 것이 이 요청의 목적이다.
 *
 * ### 단 수 사다리 (아래 media 규칙 셋이 한 짝이다. 하나만 고치지 말 것)
 *
 *   ~480px 까지     v1.68.0: swipe row (one card 82% wide + next card peeking, scroll-snap).
 *                   Rules live at the END of this file (v1.68.0 block). Before 1.68.0 this
 *                   band was one card per row, which is what 대표님 saw on a 390 phone.
 *   481px 부터      두 칸 (v1.68.0: was 540. 540 는 대표님 폰 실측 폭, NOTES 8bx)
 *   1280px 부터     세 칸 - 경기 · 장터 카드(`.wrap > .stack`, `.mk-list`)와 같은 사다리
 *
 * ### 폰(540)에서 갈라 놓아도 v1.21.0 의 고장이 아닌 이유
 *
 * v1.21.0 은 560px 에서 두 단으로 갈라 놓았다가 도로 지웠다(app.css 위쪽 주석). 그때
 * 반토막 난 것은 **서류 그림 한 장 전체**였다. 559px 창 525x742 가 560px 창에서
 * 252x356 이 되어 1920 모니터의 서류가 390 폰의 것보다 작아졌다. 공지 카드는 정반대다.
 * 카드의 몸은 **150px 높이의 작은 미리보기 사진**이고(대표님: 「사진들어가는건 크기를
 * 4분의1만해도됩니다」), 이 사진은 칸이 넓으나 좁으나 비율대로 100x150 으로 그대로
 * 선다. 칸을 둘로 나누어 작아지는 것은 글자가 감기는 폭뿐이라 세로 길이가 통째로
 * 줄어든다. 미리보기 사진을 다시 크게 만드는 쪽으로 이 사다리를 되돌리지 말 것.
 *
 * ### 360 · 390 폰을 두 칸으로 나누지 않는 이유 (v1.68.0 부터는 가로 스와이프 줄)
 *
 * 390px 본문은 362px 이라 두 칸이면 175px 씩이다. `fs-lg`(20px) 제목이 두세 줄로
 * 감겨 어르신 화면에서 칸의 묶음이 문장으로 읽힌다. 그래서 이 폭에서는 한 칸이다.
 * 실측 가로 넘침 0 은 scripts/ui-gate.js 가 매 판 확인한다.
 *
 * ### home.css 의 옛 두 단 규칙은 여기로 모았다
 *
 * home.css 에는 768 에서 `.pub .notice-list` 를 두 단으로 만드는 규칙이 있었다
 * (v1.21.0). 540 부터 두 칸인 이 사다리에 흡수되므로 지우고 **여기 한 곳**에서 낸다.
 * home.css 에서 같은 규칙을 다시 적지 말 것. 두 곳에 두면 폭 사다리를 고칠 때 한쪽만
 * 고쳐진다.
 */
/* v1.68.0: the 2-column start moved from 540 to 481 so there is no one-column band left
   between the <=480 swipe row (end of this file) and this grid. */
@media (min-width: 481px) {
  .notice-list { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}
@media (min-width: 1280px) {
  /* 위 540 규칙보다 뒤에 와야 이긴다. 같은 점수(specificity) 규칙은 파일에서
     뒤에 온 것이 이기기 때문이다. v1.64.0 에서 이 규칙을 파일 앞쪽 1280 블록에
     두었다가 1280 에서 두 칸으로 무너진 것을 에이전트 브라우저 실측으로 잡았다. */
  .notice-list { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; }
}

.notice {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  padding: var(--s4) var(--s4) var(--s3);
}
/* 고정 딱지와 제목이 한 줄에서 시작한다. 딱지가 없으면 제목이 그대로 왼쪽 끝이다. */
.notice .hd { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
.notice .hd h3 {
  font-size: var(--fs-lg); font-weight: 800; line-height: var(--lh-tight);
  letter-spacing: -0.3px; word-break: keep-all; margin: 0;
}
/* 날짜. --ink-3 는 흰 바탕 6.08:1 이다. 더 흐리게 낮추지 말 것(tokens.css 참조). */
.notice .dt { margin-top: var(--s1); font-size: var(--fs-sm); color: var(--ink-3); font-weight: 700; }
.notice p {
  font-size: var(--fs-md); line-height: 1.75; color: var(--ink); margin: var(--s3) 0 0;
  word-break: keep-all; white-space: normal;
}
/* [자세히 보기]. 글자 링크지만 누르는 것이므로 48px 자리를 그대로 지킨다. */
.notice .more {
  display: inline-flex; align-items: center; min-height: var(--tap); margin-top: var(--s1);
  font-size: var(--fs-base); font-weight: 700; color: var(--turf-dark);
  text-decoration: underline; text-underline-offset: 4px;
}

/* ---------------------------------------------------- 공지 첨부 (v1.25.0)
 *
 * 고객이 그대로 적어 보낸 말: "젤위에 공지사항 파일넣고도할수있나요" 와
 * "대회라던지 알릴수있는공간 사진파일이라던지". 즉 여기 붙는 사진은 **대회 포스터**다.
 *
 * ### 규칙 1. 포스터를 자르지 않는다 (제일 중요하다)
 *
 * 한국 대회 포스터는 세로로 길고(3:4 이상) **제목 · 날짜 · 연락처가 위아래 끝**에 있다.
 * 그래서 높이를 고정하거나 object-fit: cover 를 쓰면 정확히 그 글자들이 잘려 나간다.
 * 이 저장소는 이미 한 번 그렇게 세로 배너의 금색 제목을 통째로 날렸다.
 *
 *   height: auto  = 비율 그대로. 이것이 기본이고 대부분 여기서 끝난다.
 *   max-height    = 아주 긴 세로 그림(1:3 같은 것)이 화면을 통째로 먹지 않게 하는 상한.
 *                   이때 object-fit: **contain** 이라 남는 폭이 여백이 될 뿐 잘리지 않는다.
 *
 * `cover` 로 바꾸지 말 것. `aspect-ratio` 를 고정하지 말 것.
 *
 * ### 규칙 2. 포스터가 곧 공지다
 *
 * 회원은 본문 글자가 아니라 그림을 읽는다. 그래서 폰에서 **본문 폭을 꽉 채운다**
 * (작은 썸네일로 옆에 붙이지 않는다). 넓은 화면에서는 560px 에서 멈춘다 - 지정서 회전
 * 상자가 같은 이유로 정한 값이고, 그 이상은 1280px 짜리 그림 한 장이 화면을 삼킨다.
 */
/* ------------------------------------------- 사진칸 `.pic-tile` (v1.27.0 · v1.28.0 에서 부품화)
 *
 * **한 부품이다. 공지 첨부 사진과 홍보 포스터가 이 한 벌을 같이 쓴다.**
 *
 * v1.27.0 에서는 이 규칙이 `.notice-pic` 이라는 이름으로 공지 첨부에만 붙어 있었고,
 * 홍보 포스터(소개 홈페이지 맨 위)는 home.css 에서 높이 상한만 걸린 채 **저마다 제
 * 비율대로 크게** 서 있었다. 그래서 대표님 화면에는 933x1400 세로 셋과 1223x1286
 * 정사각 하나가 나란히 서서 아래 끝이 들쭉날쭉했다(운영 실측: 390px 에서 높이가
 * 534 · 534 · 556 · 374 로 **세 가지**. evidence/v1280/measure-posters-before-prod.txt).
 *
 * 대표님이 하신 말씀은 한 번인데 그 말이 닿는 화면이 둘이었다. 그래서 v1.28.0 에서
 * 규칙을 **한 벌로 합쳤다.** 비슷한 두 벌로 두면 다음에 크기를 손볼 때 한쪽만 고치고
 * 통과시키게 된다(이번이 정확히 그 일이 벌어진 뒤다).
 *
 *   공지 첨부   views/partials/notice-article.ejs   <a class="notice-pic pic-tile">
 *   홍보 포스터 views/partials/posters.ejs          <a class="poster pic-tile">
 *
 * 화면마다 다른 것은 **바깥 배치뿐**이다(공지는 줄바꿈되는 가로 나열 `.notice-pics`,
 * 포스터는 회전 상자 한 칸). 안쪽 규칙은 여기 하나다. 여기를 고치면 두 화면이 같이
 * 바뀐다 - 그것이 이 부품의 목적이다. 한쪽만 다르게 만들고 싶어지면 그 이유를 먼저
 * 이 주석에 적을 것.
 *
 * 대표님 말씀 두 마디가 이 절을 만들었다.
 *
 *   (1) "사진크기가 제각각이던데 어떻게 맞추나요. 공지란에"
 *   (2) "사진들어가는건 크기를 4분의1만해도됩니다. 눌럿을때 커게 나오면될듯합니다"
 *
 * v1.25.0~v1.26.0 에서는 그림마다 제 비율대로 본문 폭을 꽉 채웠다. 자르지 않겠다는
 * 계약은 지켰지만 세로 포스터는 길쭉하고 가로 사진은 납작해서 공지란이 들쭉날쭉했다.
 *
 * (2)가 (1)의 답이기도 하다. **작은 미리보기 한 줄**로 세우면 줄이 가지런해지고,
 * 큰 그림은 눌러서 새 창에서 원본으로 본다. 그래서 큰 그림을 본문에 세우지 않는다.
 *
 * ### 지켜야 하는 것
 *
 *  - **자르지 않는다.** `object-fit: contain` 이고 `cover` 는 이 저장소에서 영구 금지다.
 *    포스터는 위쪽 끝에 대회 이름, 아래쪽 끝에 전화번호가 있다. 네모로 자르면 목록에서
 *    어느 포스터인지 알아볼 수 있게 해 주는 바로 그 두 곳이 먼저 사라진다.
 *  - **누르는 자리 48px 바닥.** 미리보기가 작아진 만큼 이 바닥에 훨씬 가까워졌다.
 *    그림이 아직 안 떴을 때도 지켜져야 한다(v1.26.0 에서 336x35 로 무너진 그 사고).
 *    그래서 칸 크기를 그림이 아니라 **CSS 와 `--ar`** 이 정한다.
 *  - **`--ar` 을 지우지 말 것.** 마크업이 서버가 아는 width/height 로 넘겨 주는 비율이고,
 *    그림이 뜨기 전에도 칸이 제 크기로 서 있게 하는 유일한 근거다(레이아웃이 안 밀린다).
 *
 * ### 크기를 왜 이 숫자로 골랐나
 *
 * 대표님 말씀은 "4분의 1" 이지만 픽셀 지정이 아니라 방향이다(작게, 눌러서 크게).
 * 390px 폰에서 예전 세로 포스터는 **536px** 높이였다. 그 4분의 1은 134px 이고,
 * 2:3 포스터라면 폭이 89px 이다. 실제로 세워 보니 어느 포스터인지 알아보기 어려웠다.
 * 그래서 **150px**(폰) / **180px**(768px 이상)로 잡았다. 예전 높이의 약 28% 이고
 * 넓이로는 약 8% 라 대표님이 말씀하신 "작게" 를 넉넉히 만족하면서, 2:3 포스터가
 * 100x150 으로 서서 제목 띠와 색으로 구별이 된다. 더 줄이지 말 것.
 *
 * 이 크기에서는 폰 본문 폭(360px 에서 약 296px) 안에 가로 사진도 그대로 들어가므로
 * **빈 띠(레터박스)가 생기지 않는다.** 위의 contain 은 아주 넓은 파노라마를 위한 낙하산이다.
 */
.notice-pics {
  display: flex; flex-wrap: wrap; align-items: flex-start;
  gap: var(--s2); margin-top: var(--s3);
}
.pic-tile {
  display: block; text-decoration: none;
  /* 미리보기 높이. 위 "크기를 왜 이 숫자로" 참조. */
  --nh: 150px;
  max-width: 100%;
  /* 누름 자리 바닥. 그림이 안 떠도, `--ar` 이 없어도 이 아래로 무너지지 않는다. */
  min-height: var(--tap); min-width: var(--tap);
}
.pic-tile img {
  display: block;
  height: var(--nh);
  /* 폭은 비율에서 따라 나온다. `--ar` 이 없는 옛 행(width/height 가 0)이면
     세로 포스터 기본 비율로 세운다 - 칸이 0 으로 무너지는 것보다 낫다. */
  aspect-ratio: var(--ar, 3 / 4);
  width: auto;
  /* 바닥을 그림에도 건다. 아주 납작한 파노라마(10:1 같은 것)의 미리보기가
     150px 높이에서 폭만 남고 세로로 눌리는 일이 없게 한다. */
  min-height: var(--tap); min-width: var(--tap);
  max-width: 100%;
  /* 낙하산. 위 주석 참조. cover 로 바꾸지 말 것. */
  object-fit: contain;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--card-2);
}
/* "크게 보기" 안내.
 *
 * 이 클럽 회원은 **어르신**이다(그래서 알림도 문자가 아니라 알림톡으로 나간다).
 * 작은 미리보기에 아무 표시가 없으면 "누르면 커진다" 가 아니라 "사진이 깨졌다" 로
 * 읽힌다. 그래서 **늘 보이는 한글 글자**로 적는다:
 *   - 아이콘(돋보기)으로 바꾸지 말 것. 아이콘만 있는 버튼은 이 제품의 금지 사항이다.
 *   - hover 로만 보이게 하지 말 것. 폰에는 hover 가 없다.
 *   - 글자 크기는 보조 설명 바닥(15px)이다. 더 줄이지 말 것.
 * 이 줄이 누르는 자리를 세로로도 키운다(미리보기 150px + 이 줄 = 넉넉한 과녁).
 */
.pic-tile .zoom {
  display: block; text-align: center;
  font-size: var(--fs-sm); font-weight: 800; color: var(--turf-dark);
  padding: 6px 4px 2px; text-decoration: underline;
}
/* 눌린 표시. 움직이지 않고 색만 바뀐다(이 제품의 규칙). 포스터가 v1.26.0 부터
   하던 것을 부품으로 올렸다 - 이제 공지 첨부에서도 눌린 것이 보인다. */
.pic-tile:active img { border-color: var(--ink-3); background: var(--line-2); }
/* 홈에서 접어 둔 나머지 사진 수. 안내 글자이지 누르는 것이 아니다. */
.notice-more-pic { font-size: var(--fs-base); color: var(--ink-2); font-weight: 700; margin: var(--s2) 0 0; }

@media (min-width: 768px) {
  /* 넓은 화면에서는 미리보기를 조금 키운다. 여기서도 큰 그림은 눌러서 본다. */
  .pic-tile { --nh: 180px; }
}

/* 첨부 문서(PDF · HWP). 체육회 공문이 오는 자리다.
 * 한 줄 = 한 파일이고, 줄 전체가 누르는 자리다(어르신 화면에서 작은 글자 링크 하나만
 * 누르게 만들지 않는다). 아이콘만 두지 않고 "내려받기" 라고 글자로 적는다. */
.notice-docs { list-style: none; margin: var(--s3) 0 0; padding: 0; display: grid; gap: var(--s2); }
.notice-docs > li { margin: 0; }
.notice-docs > li > a {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  min-height: var(--tap); padding: 10px 13px;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--card-2);
  text-decoration: none;
}
.notice-docs > li > a:active { background: var(--line-2); }
.notice-docs .fn {
  flex: 1 1 auto; min-width: 0; font-size: var(--fs-base); font-weight: 800; color: var(--ink);
  word-break: break-all;
}
.notice-docs .fm { flex: 0 0 auto; font-size: var(--fs-sm); font-weight: 700; color: var(--ink-3); }
.notice-docs .dl {
  flex: 0 0 auto; font-size: var(--fs-base); font-weight: 800; color: var(--turf-dark);
  text-decoration: underline; text-underline-offset: 4px;
}

/* 운영 화면(`/admin/notices`)의 공지 한 줄. 목록에서 무엇인지 알아보기만 하면 되므로
   본문은 서버가 120자로 잘라 넘긴다(전문은 [고치기] 를 누르면 아래 칸에 그대로 온다). */
.card.notice-admin .hd { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.card.notice-admin .hd .nm { font-size: var(--fs-md); font-weight: 800; line-height: 1.35; word-break: keep-all; }
.card.notice-admin .bd {
  margin: var(--s2) 0 0; font-size: var(--fs-base); line-height: 1.6; color: var(--ink-2);
  word-break: keep-all; white-space: pre-wrap;
}
.card.notice-admin .acts { display: flex; gap: var(--s2); margin-top: var(--s3); flex-wrap: wrap; }
.card.notice-admin .acts form { flex: 0 0 auto; margin: 0; }
.card.notice-admin .acts .btn { min-height: var(--tap); }

/* 운영 화면의 첨부 줄 (v1.25.0). 파일 하나 = 한 줄이고, 그 줄에서 바로 열어 보고 뺀다.
   카드 안에 카드를 만들지 않는다(테두리 없이 윗선 하나로 구역만 나눈다). */
.notice-files { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--line); }
.notice-files > .l { font-size: var(--fs-sm); font-weight: 800; color: var(--ink-2); margin-bottom: var(--s2); }
.notice-files .nf { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s2); }
.notice-files .nf .mk {
  flex: 0 0 auto; width: 52px; height: 52px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--card); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
/* 미리보기도 자르지 않는다(contain). 세로 포스터가 어떤 그림인지 알아보려면 통째로 보여야 한다. */
.notice-files .nf .mk img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.notice-files .nf .mk.doc { font-size: var(--fs-xs); font-weight: 800; color: var(--ink-2); }
.notice-files .nf .grow { flex: 1 1 140px; min-width: 0; }
.notice-files .nf .fn { font-size: var(--fs-base); font-weight: 800; line-height: 1.35; word-break: break-all; }
.notice-files .nf form { flex: 0 0 auto; margin: 0; }
.notice-files .nf .btn { min-height: var(--tap); }

/* 파일 고르기 버튼 줄. 인증서 화면이 인라인 style 로 하던 것을 여기로 옮겼다. */
.pickrow { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }

/* ------------------------------------------------------------------ 가맹점 적립/사용 방향
 *
 * v1.27.0. 대표님 그림의 2단계(가맹점 이용 적립)가 들어오면서 가맹점 화면에서
 * **한 화면에 반대 방향 두 개**를 고르게 됐다. 잘못 누르면 손님에게 포인트를 거저 주거나
 * 손님 잔액을 빼앗는 화면이라, 두 버튼이 절대로 같아 보이면 안 된다.
 *
 * 그래서 색을 나눈다. 새 색을 만들지 않고 이미 뜻이 정해진 토큰 두 개를 그대로 빌린다:
 *   적립 = 초록(--turf) 계열. 이 제품에서 초록은 "좋은 쪽으로 진행" 한 가지 뜻이다.
 *   사용 = 주황빛 갈색(--hold) 계열. "잔액이 줄어드는 일" 이라 초록을 쓰면 안 된다.
 * **빨강(--warn)을 쓰지 않았다.** 사용은 오류나 취소가 아니라 정상 거래이고, 빨강을
 * 쓰면 사장님이 매 결제마다 무언가 잘못됐다고 읽으신다.
 *
 * 대비는 전부 토큰 주석의 실측값 그대로다(--turf-dark 흰 바탕 9.00:1,
 * --hold 흰 바탕 7.12:1 · --hold-bg 위 6.47:1). 색을 밝게 되돌리지 말 것.
 *
 * 색만으로 가르지 않는다는 것이 이 절의 두 번째 규칙이다. 색을 못 보는 사장님도
 * 계시므로 제목(적립해 주기 / 포인트 받기) · 설명(늘어납니다 / 줄어듭니다) ·
 * 확정 버튼 글자(적립합니다 / 사용합니다)가 전부 다르다. 색은 거들 뿐이다. */

.dir-pick { display: grid; gap: 12px; }
@media (min-width: 560px) {
  /* 넓은 화면에서는 나란히 선다. 좁은 화면에서 가로로 두면 설명 글이 두 글자씩 끊긴다. */
  .dir-pick { grid-template-columns: 1fr 1fr; }
}
.dir {
  display: block; width: 100%; text-align: left; cursor: pointer;
  /* 두 버튼 다 최소 --hero-h 보다 크다. 손가락이 닿는 자리라 넉넉히 잡는다. */
  min-height: 132px; padding: 16px 18px;
  border-radius: var(--r); border: 3px solid var(--line);
  background: var(--card); color: var(--ink); font-family: inherit;
}
.dir .dh { display: block; font-size: var(--fs-xl); font-weight: 800; letter-spacing: -0.5px; }
.dir .ds { display: block; margin-top: 7px; font-size: var(--fs-base); font-weight: 600; color: var(--ink-2); line-height: 1.5; }
.dir .ds b { font-weight: 800; color: var(--ink); }
.dir .dr { display: block; margin-top: 9px; font-size: var(--fs-sm); font-weight: 800; }
.dir.earn { border-color: var(--turf); background: var(--turf-bg); }
.dir.earn .dh, .dir.earn .dr { color: var(--turf-dark); }
.dir.earn:active { background: #d5e8dd; }
.dir.spend { border-color: var(--hold); background: var(--hold-bg); }
.dir.spend .dh, .dir.spend .dr { color: var(--hold); }
.dir.spend:active { background: #f7e8cd; }

/* 방향이 정해진 뒤의 상자(금액 · 확인 · 완료). 상자 색과 꼬리표가 방향을 계속 말한다.
   여기서 카드 안에 카드를 만들지 않는다 - 꼬리표는 테두리 없는 글자 줄 하나다. */
.dirbox { border-width: 3px; }
.dirbox.earn { border-color: var(--turf); }
.dirbox.spend { border-color: var(--hold); }
.dirtag { font-size: var(--fs-md); font-weight: 800; margin-bottom: var(--s2); }
.dirbox.earn .dirtag { color: var(--turf-dark); }
.dirbox.spend .dirtag { color: var(--hold); }
.who-card { text-align: center; border-color: var(--turf-line); background: var(--turf-bg); }
.who-name { font-size: 28px; font-weight: 800; }
.who-line { font-size: var(--fs-md); color: var(--ink-2); font-weight: 600; }
.who-line b { color: var(--ink); font-weight: 800; }

/* 넣는 동안 계산 결과를 보여 주는 줄. **본문 크기다.** 회색 작은 글씨로 낮추지 말 것 -
   0 을 하나 더 누르신 것을 알아채는 자리가 여기 하나다. */
.calc { margin-top: 10px; font-size: var(--fs-lg); font-weight: 800; text-align: center; color: var(--ink); min-height: 28px; }

/* 확인 문장. 이름 · 숫자 · 방향이 한 문장에 다 들어간다. */
.confirm-line { margin: 0; font-size: var(--fs-lg); font-weight: 700; line-height: 1.5; text-align: center; }
.confirm-line b { font-weight: 800; }
.confirm-line .big { display: inline-block; margin: 6px 0 2px; font-size: 40px; font-weight: 800; letter-spacing: -1.5px; font-variant-numeric: tabular-nums; }
.dirbox.earn .confirm-line .big, .dirbox.earn .confirm-line .way { color: var(--turf-dark); }
.dirbox.spend .confirm-line .big, .dirbox.spend .confirm-line .way { color: var(--hold); }
.confirm-sub { margin: 12px 0 0; font-size: var(--fs-base); color: var(--ink-2); font-weight: 600; line-height: 1.55; text-align: center; }

/* 확정 버튼도 방향 색을 입는다. 초록 위 흰 글씨 7.61:1, --hold 위 흰 글씨 7.12:1. */
.hero-action.earn { background: var(--turf); }
.hero-action.earn:active { background: var(--turf-dark); }
.hero-action.spend { background: var(--hold); }
.hero-action.spend:active { background: #5f3d04; }

/* "또는" 구분줄 (v1.27.0). 참가비 화면에서 [입금했어요] 와 [포인트로 내기] 사이에 선다.
   두 방법이 **나란한 선택지**라는 것을 한 눈에 말하는 자리이고, 이것이 없으면 아래 칸이
   위 버튼의 부속처럼 읽힌다. 글자는 본문 크기다(작은 회색으로 낮추지 말 것). */
.orline { display: flex; align-items: center; gap: var(--s3); margin: var(--s5) 0 var(--s4); }
.orline::before, .orline::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.orline > span { font-size: var(--fs-base); font-weight: 800; color: var(--ink-2); }

/* 포인트 내역 줄에서 **가게 이름**. 제목 줄로 끌어올린 조각이라 종류 이름과 붙어 서지만,
   같은 무게로 두면 어느 쪽이 종류인지 헷갈린다. 배지처럼 테두리 한 겹으로 갈라 놓는다.
   15px, --ink-2 는 흰 바탕 8.81:1(--fs-xs 바닥 14px 위). 회색으로 더 낮추지 말 것. */
.list .li .t .ptx-where {
  font-size: var(--fs-sm); font-weight: 800; color: var(--ink-2);
  padding: 2px 8px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--card-2);
}

/* ------------------------------------------------------------------ 중고 장터 (v1.27.0)
 *
 * 대표님이 포인트 그림과 따로 부탁하신 자리. 회원끼리 쓰던 운동 장비를 넘긴다.
 *
 * ### 사진을 자르지 않는다 (이 절의 계약)
 *
 * 중고 물건 사진은 세로로 찍은 폰 사진이 대부분이고, 사려는 사람이 확인하려는 것은
 * 채 끝의 흠집이나 라켓 줄 상태처럼 **사진 가장자리**에 있다. 가운데만 남기면 사진을
 * 붙인 뜻이 사라진다. 그래서 공지 첨부(.notice-pic) · 홍보 포스터(.posters)와 **같은
 * 규칙**이다: width/height 는 auto 로 두고 상한만 걸어 브라우저가 비율을 지키게 한다.
 * `object-fit: cover` 나 고정 `aspect-ratio` 를 넣지 말 것.
 *
 * ### 아직 안 내려온 사진이 누르는 자리를 무너뜨리지 않는다
 *
 * v1.26.0 에서 실제로 겪은 사고다(NOTES 8af): `loading="lazy"` 인 그림이 아직 안 왔을 때
 * 폭이 auto 라 칸이 2px 로 줄고 링크가 336x35 가 됐다(바닥 48x48). **캐시가 빈 첫
 * 방문자에게만** 나타나서 더 나빴다. 그래서 여기서는 처음부터 두 겹으로 막는다:
 *   1. 목록 줄(.mk-item)에 `min-height: 88px` — 그림이 없어도 줄이 안 무너진다.
 *   2. 사진칸(.mk-thumb)에 **고정 크기 88x88** — 그림이 오기 전에도 자리가 잡혀 있다.
 *      썸네일이라 크기를 정해도 되고(상세 화면의 큰 사진은 auto 로 둔다), contain 이라
 *      그 안에서 비율은 그대로 지켜진다.
 * 상세 화면의 큰 사진(.mk-photo)은 폭이 auto 여야 하므로 **누르는 자리 쪽에** 바닥을
 * 세운다(포스터에서 쓴 것과 같은 수법. 그림에 폭을 주면 레터박스가 생긴다). */

.mk-list { display: grid; gap: 10px; }
.mk-item {
  display: flex; align-items: center; gap: 13px;
  /* 88(사진) + 위아래 여백. 그림이 안 왔어도 이 아래로 내려가지 않는다. */
  min-height: 88px; padding: 12px 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  text-decoration: none; color: inherit;
}
.mk-item:active { background: var(--card-2); }
/* 판매완료는 흐리게 하지 않는다. 흐리게 하면 대비가 바닥 아래로 내려간다.
   대신 왼쪽에 회색 띠를 세우고 배지 글자로 말한다(색만으로 가르지 않는다). */
.mk-item.sold { background: var(--card-2); border-left: 6px solid var(--ink-3); }
.mk-thumb {
  flex: 0 0 auto; width: 88px; height: 88px; border-radius: var(--r-sm);
  border: 1px solid var(--line-2); background: var(--bg);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
/* 썸네일 안에서도 자르지 않는다. 칸 크기는 정해져 있고 그림이 그 안에 통째로 들어간다. */
.mk-thumb img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.mk-nopic { font-size: var(--fs-xs); font-weight: 700; color: var(--ink-3); text-align: center; line-height: 1.35; }
.mk-body { flex: 1 1 auto; min-width: 0; display: block; }
.mk-title { display: block; font-size: var(--fs-md); font-weight: 800; line-height: 1.35; word-break: keep-all; }
.mk-price { display: block; margin-top: 3px; font-size: var(--fs-lg); font-weight: 800; color: var(--turf-dark); font-variant-numeric: tabular-nums; }
.mk-meta { display: block; margin-top: 3px; font-size: var(--fs-sm); color: var(--ink-2); }
.mk-badge { flex: 0 0 auto; align-self: flex-start; }

.mk-head { margin-bottom: var(--s4); }
.mk-price-big { font-size: var(--fs-2xl); font-weight: 800; color: var(--turf-dark); letter-spacing: -0.8px; font-variant-numeric: tabular-nums; margin-top: 4px; }
.mk-meta-line { margin-top: 4px; font-size: var(--fs-base); color: var(--ink-2); font-weight: 600; }

.mk-photos { display: grid; gap: var(--s3); margin-bottom: var(--s4); }
/* 누르는 자리에 바닥을 세운다. **그림 쪽에 폭을 주지 말 것**(레터박스가 생긴다). */
.mk-photo { display: block; min-height: var(--tap); text-decoration: none; }
.mk-photo img {
  display: block; width: auto; height: auto;
  max-width: 100%; max-height: 900px; object-fit: contain;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--card-2);
}
@media (min-width: 768px) {
  /* 넓은 화면에서 폰 사진 한 장이 벽이 되지 않게 묶는다(공지 첨부와 같은 560px). */
  .mk-photo img { max-width: 560px; max-height: 1120px; }
}

.mk-body-card p { margin: 0 0 var(--s3); font-size: var(--fs-base); line-height: var(--lh-body); word-break: keep-all; }
.mk-body-card p:last-child { margin-bottom: 0; }
.mk-contact { border-color: var(--turf-line); background: var(--turf-bg); }
.mk-contact .btn.primary .i { width: 22px; height: 22px; }

/* 글 고치기 화면의 "올린 사진" 한 줄. 공지 첨부 줄(.notice-files .nf)과 같은 모양이다. */
.mk-photo-row { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; padding: var(--s2) 0; border-bottom: 1px solid var(--line-2); }
.mk-photo-row:last-child { border-bottom: 0; }
.mk-photo-row .mk {
  flex: 0 0 auto; width: 56px; height: 56px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--card); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.mk-photo-row .mk img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.mk-photo-row .grow { flex: 1 1 120px; min-width: 0; }
.mk-photo-row .fn { font-size: var(--fs-base); font-weight: 700; word-break: break-all; }

/* 빈 화면 안의 글자 링크. 누르는 자리라 48px 바닥을 지킨다. */
.ulink {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap); padding: 0 var(--s3);
  font-size: var(--fs-base); font-weight: 800; color: var(--turf-dark);
  text-decoration: underline; text-underline-offset: 4px;
}

/* 큰 줄 하나로 다른 화면에 들어가는 자리 (v1.27.0. [내정보] → 중고 장터).
 * 하단 탭에 칸을 더하는 대신 쓰는 방법이다. 줄 전체가 누르는 자리이고, 오른쪽에
 * "열기" 라고 **글자로** 적는다(화살표만 두면 이 사용자층에게는 장식으로 읽힌다). */
.entry-row {
  display: flex; align-items: center; gap: 13px;
  min-height: 76px; margin-top: var(--s3); padding: 14px 16px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  text-decoration: none; color: inherit;
}
.entry-row:active { background: var(--card-2); }
.entry-row .er-mark {
  flex: 0 0 auto; width: 46px; height: 46px; border-radius: var(--r-sm);
  background: var(--turf-bg); color: var(--turf-dark); border: 1.5px solid var(--turf-line);
  display: grid; place-items: center;
}
.entry-row .er-mark .i { width: 24px; height: 24px; }
.entry-row .er-body { flex: 1 1 auto; min-width: 0; }
.entry-row .er-t { display: block; font-size: var(--fs-md); font-weight: 800; }
.entry-row .er-s { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.45; }
.entry-row .er-go { flex: 0 0 auto; font-size: var(--fs-base); font-weight: 800; color: var(--turf-dark); text-decoration: underline; text-underline-offset: 4px; }

/* ------------------------------------------------------------------ 구장 예약 (v1.32.0)
 *
 * 대표님 말씀 세 줄이 이 구역을 만들었다 (2026-09-10):
 *
 *   「구장이름도 좀 크게해야 눈에 띌꺼같아요」
 *   「스크린 개인 예약시 6시면 각각 들어오고 표시나게 되어될듯합니다」
 *   「그리고예약은 팀 먼저. 개인순으로 부탁드려요」 · 「팀예약 젤위에」
 *
 * 글씨 크기는 **전부 tokens.css 의 사다리(--fs-*)에서 가져온다.** 여기서 새 픽셀 값을
 * 지어내지 말 것 — 이 앱은 화면에 그려진 글씨를 실제로 재서 통과시키는 검사(ui-gate)를
 * 쓰고, 사다리 밖의 값은 그 검사가 지켜 주지 못한다.
 */

/* 구장 고르기 칩. 16px → --fs-md(18px). 이 화면에서 구장을 고르는 유일한 자리인데
   날짜 글씨(19px)보다 작아서 "눈에 안 띄는" 상태였다. */
.chip.venue { font-size: var(--fs-md); font-weight: 800; padding: 10px 20px; }

/* 지금 보고 있는 구장의 이름. 칩이 여러 개 나란히 있으면 어느 것을 고른 상태인지
   읽히지 않아서, 고른 구장을 **한 번 더 크게** 세운다. */
.venue-head { margin: 0 0 var(--s3); }
.venue-head .vname {
  font-size: var(--fs-xl); font-weight: 800; line-height: var(--lh-tight);
  margin: 0; word-break: keep-all;
}
.venue-head .vsub {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  margin-top: 6px; font-size: var(--fs-base); color: var(--ink-2); font-weight: 600;
}
.venue-head .vsub b { font-weight: 800; color: var(--ink); }

/* 「팀예약 젤위에」. 시간표 **위**에 서는 한 줄짜리 입구다.
   초록 주 버튼(.hero-action)으로 만들지 않았다 — 이 화면의 주 동작은 칸마다 있는
   [예약] 이고, 한 화면에 초록 버튼은 하나라는 규칙(NOTES 2장)을 깨지 않는다. */
.team-entry {
  display: flex; align-items: center; gap: 13px;
  min-height: 76px; margin: 0 0 var(--s3); padding: 14px 16px;
  background: var(--card); border: 1.5px solid var(--turf-line); border-radius: var(--r);
  text-decoration: none; color: inherit;
}
.team-entry:active { background: var(--turf-bg); }
.team-entry .grow { flex: 1 1 auto; min-width: 0; }
.team-entry .t { display: block; font-size: var(--fs-md); font-weight: 800; }
.team-entry .s { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.45; }
.team-entry .i { width: 24px; height: 24px; flex: 0 0 auto; color: var(--turf-dark); }

/* 시간표 한 칸. 이제 한 칸에 여러 줄이 들어가므로 위쪽 정렬이다
   (가운데 정렬이면 여섯 줄짜리 칸에서 [예약] 버튼이 허공에 뜬다). */
.slotlist .li.slot { align-items: flex-start; }
.slotlist .li.slot .avatar { margin-top: 2px; }
.slotlist .li.slot form,
.slotlist .li.slot > .badge { margin-top: 2px; }
.slotlist .li.slot.full { background: var(--card-2); }

/* 「표시나게」 — 몇 자리 중 몇 자리가 찼는지. 시간 바로 옆이다. */
.slotlist .seatcnt {
  font-size: var(--fs-sm); font-weight: 800; color: var(--turf-dark);
  background: var(--turf-bg); border: 1px solid var(--turf-line);
  border-radius: var(--r-sm); padding: 2px 8px; font-variant-numeric: tabular-nums;
}
.slotlist .seatcnt.full { color: var(--warn); background: var(--warn-bg); border-color: var(--warn); }

/* 한 칸 안의 예약 한 줄. 팀이 먼저, 개인이 아래(순서는 서버가 정한다).
   줄마다 [취소] / [예약 풀기] 가 붙으므로 세로 여백을 준다. */
.slotlist .slotrow {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: var(--fs-base); color: var(--ink); font-weight: 600;
  padding: 6px 0; border-bottom: 1px dashed var(--line-2);
}
.slotlist .slotrow:last-child { border-bottom: 0; }
.slotlist .slotrow b { font-weight: 800; }

/* 구장 이름 한 단계 크게 (v1.32.0, 「구장이름도 좀 크게해야 눈에 띌꺼같아요」).
 * 경기 카드의 [장소] 줄과 경기 상세의 [장소] 값에만 붙인다. 옆 줄(일시·참가비)까지
 * 같이 키우면 카드 안에서 아무것도 눈에 띄지 않는다. */
.match-card .meta .vname { font-size: var(--fs-md); font-weight: 800; color: var(--ink); }
.kv .v.vname { font-size: var(--fs-md); font-weight: 800; }

/* 시간 고르기 칸에 붙는 「N자리」 (v1.32.0). 한 칸에 여러 명이 들어가게 되면서
 * "이 시간이 비었나" 가 아니라 "몇 자리 남았나" 가 고르는 기준이 되었다. */
.pickers .pick span .left {
  display: block; margin-top: 2px; font-size: var(--fs-xs); font-weight: 700; opacity: 0.85;
}

/* 지금 정원을 숫자로 보여 주는 자리 (v1.32.0, /admin/slotcaps).
 * 곱셈 결과를 대표님이 암산하지 않으셔도 되게 계산된 값을 그대로 세운다. */
.capnow {
  font-size: var(--fs-base); font-weight: 700; color: var(--ink-2);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 4px 10px;
}
.capnow b { font-weight: 800; color: var(--ink); }

/* 가맹점 사진 여러 장 (v1.32.0, 「가맹점 사진도 여러장올리게끔 부탁합니다」).
 * 하단 탭의 /merchants 목록에서만 쓴다. 회원 홈의 돌아가는 상자는 대표 사진 한 장이다.
 * **자르지 않는다**: 간판 사진은 가게 이름이 사진 가장자리에 있는 경우가 많아
 * object-fit: cover 로 잘라 내면 그 글자가 먼저 사라진다(장터 사진과 같은 이유). */
.list .li.mch { align-items: flex-start; }
.list .li.mch .avatar.photo { padding: 0; overflow: hidden; background: #fff; }
.list .li.mch .avatar.photo img { width: 100%; height: 100%; object-fit: contain; }
.mph-strip {
  display: flex; gap: 8px; margin-top: 8px; overflow-x: auto; scrollbar-width: none;
  padding-bottom: 2px;
}
.mph-strip::-webkit-scrollbar { display: none; }
.mph-strip .sh {
  flex: 0 0 auto; height: 84px; border-radius: var(--r-sm); border: 1px solid var(--line-2);
  background: #fff; overflow: hidden; display: grid; place-items: center;
}
.mph-strip .sh img { height: 84px; width: auto; max-width: 140px; object-fit: contain; display: block; }

/* ------------------------------------------------------- 전자쿠폰 (v1.37.0)
 *
 * 대표님이 쿠폰 그림을 보내시며 그대로 적어 보내신 말:
 * **「이거 홈페이지에서 전자쿠폰형식으로 다운 받을수있나요」**
 *
 * **이 절은 app.css 에 있어야 한다.** 회원 홈과 소개 홈페이지가 같은 partial 을 쓰는데
 * home.css 는 소개 홈페이지에만 실린다(views/partials/head.ejs). 여기 있으면 두 화면이
 * 같은 모양이고, home.css 로 옮기는 순간 회원 홈에서 카드가 통째로 벗겨진다.
 *
 * 제목은 서류 칸(.certs)과 **같은 규칙**이다: 회원 앱에서는 왼쪽 굵은 선 + 20px,
 * 소개 홈페이지에서는 home.css 의 `.sect > h2`(48px)가 특이도 동점으로 나중에 읽혀 이긴다.
 * 여기에 쿠폰 전용 제목 크기를 적지 말 것 - 적는 순간 두 화면의 구역 제목이 갈라진다.
 */
.coupons { margin-top: 26px; }
.coupons > h2 {
  font-size: var(--fs-lg); font-weight: 800; line-height: var(--lh-tight);
  border-left: 4px solid var(--ink); padding-left: 10px;
}
.coupon-list { list-style: none; margin: var(--s3) 0 0; padding: 0; display: grid; gap: var(--s3); }

/* 카드는 다른 카드와 같다: 그림자로 띄우지 않고 실선 한 겹으로 가른다(app.css 의 .card 주석).
 * 폰에서는 **세로로 쌓는다.** 쿠폰 그림이 가로로 넓어(1595x986) 옆에 글을 세우면
 * 390px 화면에서 글 칸이 150px 남짓으로 줄고 [쿠폰 받기] 글자가 두 줄로 접힌다. */
.coupon {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  padding: var(--s4);
}
/* 미리보기 높이만 이 화면 값으로 바꾼다(부품 자체는 .pic-tile 그대로).
 * 150px(포스터 기본)보다 낮춘 이유: 이 카드는 회원 홈에서 경기 목록 **위**에 서므로
 * 여기서 30px 를 벌면 목록이 그만큼 위로 온다. 쿠폰 글씨를 읽는 자리가 아니라 "무엇인지
 * 알아보는" 자리다. 조건을 읽으실 분은 [크게 보기] 나 [쿠폰 받기] 로 원본을 보신다.
 * **더 줄이지는 말 것** - 100px 아래로 내리면 쿠폰 제목 글자가 미리보기에서 안 읽힌다. */
.coupon .shot { --nh: 120px; }
.coupon .tx { margin-top: var(--s3); }
.coupon .tx h3 { font-size: var(--fs-md); font-weight: 800; line-height: var(--lh-tight); }
.coupon .ln { margin: var(--s2) 0 0; font-size: var(--fs-base); line-height: 1.55; color: var(--ink); }
/* 유효기간은 이 카드에서 제일 중요한 한 줄이다. 지난 쿠폰을 들고 매장에 가시면
 * 그 전화는 클럽으로 온다(만료 뒤에는 구역 자체가 사라진다. src/lib/coupon.ts). */
.coupon .ln.when b { font-weight: 800; color: var(--warn); }
.coupon .ln.terms { font-size: var(--fs-sm); color: var(--ink-2); }
/* 남은 수량 (v1.38.0. 대표님: 「무료쿠폰은 100장씩 갱신되게 부탁해요」).
 *
 * 유효기간 줄(빨강)과 **색을 달리한다.** 저쪽은 "지나면 못 쓴다" 는 경고고 이쪽은 현황일
 * 뿐이다. 둘 다 빨갛게 하면 카드에 경고가 둘이 되어 어느 쪽도 안 읽힌다.
 * 줄 전체가 아니라 **숫자만** 굵고 크게 세운다(어르신 화면에서 줄이 통째로 굵으면
 * 정작 숫자가 안 튄다). 위 여백은 기본(--s2)보다 좁은 --s1 이다 - 이 카드는 회원 홈에서
 * 경기 목록 **위**에 서므로 한 줄이 늘 때 목록이 밀리는 양을 여기서 줄인다
 * (`.coupon .shot` 주석의 같은 이야기). */
.coupon .ln.stock { margin-top: var(--s1); font-size: var(--fs-sm); color: var(--ink-2); }
.coupon .ln.stock b { font-size: var(--fs-base); font-weight: 800; color: var(--turf-dark); }
/* 주 동작. 카드에서 제일 큰 물건이어야 한다("한 번 눌러 받는다" 가 이 판의 전부다). */
.coupon .btn.dl { margin-top: var(--s4); }
.coupon .btn.dl .i { width: 24px; height: 24px; }
.coupon .ln.note { font-size: var(--fs-sm); color: var(--ink-2); }
.coupon .ln.note b { font-weight: 800; color: var(--ink); }
/* 전화번호는 눌러서 거는 자리다. 어르신 회원이 실제로 누른다(다른 화면의 tel 링크와 같은 모양). */
.coupon .ln.note a { color: var(--turf-dark); font-weight: 800; text-decoration: underline; text-underline-offset: 3px; }

@media (min-width: 768px) {
  /* 넓은 화면에서는 그림을 왼쪽에 세우고 글을 오른쪽에 붙인다. 세로로 쌓아 두면
     빈 오른쪽이 그대로 남는다(v1.36.0 에서 첫 화면에 대해 잰 그 문제와 같은 것이다). */
  .coupon { display: flex; align-items: flex-start; gap: var(--s5); }
  .coupon .shot { --nh: 180px; flex: 0 0 auto; }
  .coupon .tx { margin-top: 0; flex: 1 1 auto; min-width: 0; }
  /* 이 폭에서는 버튼이 카드 전체를 가로지를 이유가 없다. 글 칸 안에서 제 폭만 쓴다. */
  .coupon .btn.dl { width: auto; min-width: 220px; }
}

/* ===========================================================================
 * 월회원 · 년회원 회비 칸 (v1.41.0)
 * ===========================================================================
 *
 * 대표님이 소개 홈페이지 첫 화면 띠를 스크린샷에 **파란 펜으로 동그라미** 치시고
 * 그대로 적어 보내신 말: **「여기란에. 월회원 년회원 가입할수있는공간으로」**
 *
 * ★app.css 에 있는 이유★ — `views/partials/dues.ejs` 한 벌이 **두 화면**에 선다:
 * 소개 홈페이지(home.css 를 읽는다)와 회원 홈(app.css 만 읽는다). home.css 에 두면
 * 회원 홈에서 이 칸이 스타일 없이 무너지고, 그 화면이 정확히 **대표님이 로그인해서
 * 보시는 화면**이다(routes/home.ts 의 `if (req.user) return next()`).
 * **여기서 home.css 로 옮기지 말 것.**
 *
 * 두 벌의 옷이 있다:
 *   .duesbox        흰 바탕(회원 홈 · /dues) — 카드 모양
 *   .duesbox.hero   숯색 머리판 위(소개 홈페이지 첫 화면) — 글씨가 흰색이 된다
 */
.duesbox { margin-top: var(--s7); }
.duesbox > h2 {
  font-size: var(--fs-xl); font-weight: 800; line-height: var(--lh-tight); letter-spacing: -0.5px;
  border-left: 5px solid var(--ink); padding-left: var(--s3); margin: 0;
}
/* 첫 화면 띠 안에서는 제목을 한 급 줄인다. 그 위에 이미 h1 이 있어서 같은 크기로 두면
   화면에 큰 제목이 둘이 되고, 어느 쪽이 이 화면의 이름인지 읽히지 않는다. */
.duesbox > h2.mini {
  font-size: var(--fs-md); font-weight: 800; letter-spacing: -0.3px;
  border-left: none; padding-left: 0;
}
.duesbox > .say { margin: var(--s3) 0 0; font-size: var(--fs-base); line-height: 1.6; color: var(--ink-2); }

/* ★두 칸은 **폰에서도** 나란히 선다 (v1.46.0).★
 *
 * 대표님이 폰으로 이 칸을 보시고 그대로 적어 보내신 말: **「월회원 년회원 나란히 좀 부탁」**
 *
 * v1.45.1 까지는 560px 미만에서 세로로 쌓았다(`@media (min-width: 560px)`). 대표님 폰은
 * 390px 이라 그 규칙 아래에 있었고, 그래서 두 칸이 위아래로 서 있었다. 이제 폭과 상관없이
 * 두 단이다. **이 규칙을 다시 미디어쿼리 뒤로 숨기지 말 것** — 그 순간 대표님 화면이
 * 원래대로 돌아간다.
 *
 * `minmax(0, 1fr)` 이어야 한다. 그냥 `1fr` 이면 격자 칸의 최소 폭이 **내용 폭**이라
 * 금액("500,000원")이 칸보다 넓어지는 순간 격자가 화면 밖으로 삐져나간다. 이 사용자층에게
 * 가로 스크롤은 없는 칸이나 같다(NOTES 2장). 절대 스크롤로 만들지 않는다. */
.dues-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3); margin-top: var(--s4); }

.dues-card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: var(--s4); display: flex; flex-direction: column; gap: var(--s1);
  /* 격자 칸이 내용에 밀려 넓어지지 않게 한다(위 minmax 와 한 쌍이다). */
  min-width: 0;
}
.dues-card .nm { font-size: var(--fs-md); font-weight: 800; color: var(--ink); }
/* 금액이 이 카드에서 제일 큰 물건이다. 어르신 화면이라 숫자를 크게 세운다. */
.dues-card .fee { font-size: var(--fs-2xl); font-weight: 800; letter-spacing: -1px; color: var(--turf-dark); }
.dues-card .fee .wo { font-size: var(--fs-md); font-weight: 800; margin-left: 2px; }
/* 금액이 0원일 때. **구역을 감추지 않는다**(partials/dues.ejs 맨 위의 그 규칙).
   경고색(--warn)이 아니라 대기색(--hold)이다 — 고장이 아니라 아직 안 정하신 상태다. */
.dues-card .fee.soon { font-size: var(--fs-md); font-weight: 800; color: var(--hold); letter-spacing: 0; }
.dues-card .per { font-size: var(--fs-sm); font-weight: 700; color: var(--ink-3); }
.dues-card .ln { font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-2); }
/* 버튼은 카드 맨 아래에 붙는다. 두 카드의 설명 길이가 달라도 버튼 줄이 나란히 선다. */
.dues-card form, .dues-card > .btn { margin-top: auto; padding-top: var(--s3); }
.dues-card form { margin-bottom: 0; }
/* 카드 안의 모든 글자가 칸 안에서 접힌다. 긴 낱말(계좌·금액)이 칸을 밀어내면 위 minmax 가
   막아 주지만, 그때 글자가 잘려 보이면 안 되므로 접기까지 켜 둔다. */
.dues-card .nm, .dues-card .fee, .dues-card .per, .dues-card .ln { min-width: 0; overflow-wrap: anywhere; }

/* -------------------------------------------------- 폰 폭에서 두 단으로 설 때 (v1.46.0)
 *
 * 390px 에서 한 칸에 남는 폭은 **실측 151px**(바탕 여백 16x2 · 칸 사이 8 · 카드 안쪽 12x2).
 * 여기에 28px 짜리 금액("500,000원" = 실측 158px)을 그대로 세우면 칸 밖으로 나간다.
 * 그래서 좁은 폭에서만 금액·이름·버튼을 **한 단계씩** 내린다.
 *
 * ★글씨를 여기서 더 줄이지 말 것.★ 이 제품의 바닥은 본문 17px · 버튼 높이 60px 이고
 * (NOTES 2장) 아래 값들은 그 바닥에 딱 맞춰 놓은 것이다. 한 단계 더 줄이면 파크골프
 * 어르신들이 못 읽는다. 더 줄여야 할 만큼 글자가 길어지면 **글씨가 아니라 문구**를 줄인다. */
@media (max-width: 559px) {
  .dues-cards { gap: var(--s2); }
  .dues-card { padding: var(--s3); }
  .dues-card .nm { font-size: var(--fs-base); }
  .dues-card .fee { font-size: var(--fs-xl); letter-spacing: -0.8px; }
  .dues-card .fee .wo { font-size: var(--fs-base); }
  .dues-card .fee.soon { font-size: var(--fs-base); letter-spacing: -0.3px; }
  /* 버튼 글씨는 본문 바닥(17px)까지만 내리고 좌우 여백을 줄여 자리를 만든다.
     높이 60px 은 그대로다 — 누르는 자리는 건드리지 않는다. */
  .dues-card .btn { padding-left: var(--s2); padding-right: var(--s2); font-size: var(--fs-base); }
}

.duesbox .dues-note {
  margin: var(--s4) 0 0; font-size: var(--fs-sm); line-height: 1.6; color: var(--ink-2);
  background: var(--card-2); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: var(--s3) var(--s4);
}
.duesbox .dues-note b { font-weight: 800; color: var(--ink); }
/* 대표님(운영자)께만 보이는 줄. 회원에게는 애초에 그려지지 않는다(EJS 쪽에서 가른다). */
.duesbox .sub-actions.dues-admin { margin-top: var(--s3); }

/* ---------------------------------------------------------- 숯색 머리판 위 (첫 화면)
 * 글씨가 흰 계열이 되어야 한다. 카드 자체는 흰색을 유지한다 — 숯색 위에 숯색 카드를
 * 얹으면 카드 경계가 사라지고, 그 상태에서 대비를 지킬 방법이 없다(NOTES: 참고한
 * 사이트가 #28303A 위에 #28303A 로 칠해 대비 1:1 을 만든 그 실수). */
.duesbox.hero { margin-top: var(--s6); }
.duesbox.hero > h2, .duesbox.hero > h2.mini { color: #fff; }
.duesbox.hero > .say { color: var(--bar-ink); }
/* 숯색 바탕 대비 9.87:1. 흐린 회색으로 낮추지 말 것(tokens.css 의 --bar-ink 주석). */
.duesbox.hero .dues-note {
  background: var(--bar-2); border-color: var(--bar-line); color: var(--bar-ink);
}
.duesbox.hero .dues-note b { color: #fff; }
.duesbox.hero .sub-actions a { color: var(--bar-ink); }
.duesbox.hero :focus-visible { outline: 3px solid #fff; outline-offset: 3px; }

@media (min-width: 1024px) {
  /* 대표님은 1920 모니터를 175% 로 보신다(= 실효 1097px). 그 폭에서 카드 둘이 벽
     한가운데 늘어지지 않게 첫 화면 안에서는 폭을 묶는다.
     ★v1.60.0 정정★ — 여기 오래 적혀 있던 「`.mast-in` 이 640px 이므로」는 **틀린 말이다.**
     home.css 의 768px 이상 규칙이 `.mast-bar · .mast-in · .pub` 를 1760px 까지 이미
     열어 두었다(실측: 1920 에서 `.pub` 가 1760px). 그래서 640px 로 묶인 이 칸만 벽
     왼쪽 3분의 1 에 몰렸고, 제목(`h2.mini`)은 본문 폭 그대로라 **글자와 카드의 왼쪽
     끝이 서로 어긋나** 보였다.
     고친 값은 960px, 그리고 가운데(`auto`)가 아니라 **왼쪽 끝 고정(`margin-left: 0`)** 이다.
     제목과 카드가 같은 왼쪽 끝에 서야 어긋남이 사라진다. 실측:
       1097px: h2 960x24@61  · cards 960x222@61   → 왼쪽 끝 같음
       1920px: h2 960x24@160 · cards 960x222@160  → 왼쪽 끝 같음
     390px 은 이 규칙 밖이라 숫자가 한 글자도 안 바뀐다(문서 높이 10044 그대로). */
  .duesbox.hero .dues-cards { max-width: 960px; margin-left: 0; margin-right: auto; }
  .duesbox.hero > h2.mini { max-width: 960px; margin-left: 0; margin-right: auto; }
}

/* ==========================================================================
 * 정식 홈페이지 바로가기 (v1.45.0) — views/partials/official-site.ejs
 *
 * 대표님: 「레슨하고깊은 사람은 정식 홈페에지에 등록 대회참가 들어갈수있게 링크걸어주면
 * 좋겠습니다」. 나가는 곳은 대한체육회 플랫폼의 클럽 사이트다(우리 화면이 아니다).
 *
 * ★app.css 에 있는 이유★ — 회비 칸과 똑같다. 이 partial 한 벌이 회원 홈(app.css 만
 * 읽는다)과 소개 홈페이지(home.css 를 같이 읽는다) 두 곳에 선다. home.css 에 두면
 * **대표님이 로그인해서 보시는 화면**에서 이 칸이 스타일 없이 무너진다.
 * 여기서 home.css 로 옮기지 말 것.
 *
 * ★새 부품을 만들지 않았다★ — 줄 두 개는 이 앱이 이미 쓰는 `.list .li` 그대로다.
 * 카드·큰 버튼을 쓰면 종목 격자가 390px 화면에서 아래로 밀리는데, 대표님이 그 격자를
 * 「이화면 유지하면좋겠습니다」라고 지목하셨다. 높이를 늘리는 방향으로 고치지 말 것.
 * ========================================================================== */
.offsite { margin-top: var(--s6); }
.offsite > h2.mini {
  font-size: var(--fs-md); font-weight: 800; letter-spacing: -0.3px; margin: 0 0 var(--s3);
}
/* 종목 격자를 밀어내지 않도록 줄 높이를 목록 기본(66px)보다 한 단계 눌러 둔다.
   그래도 누르는 자리는 48px 바닥선을 넘는다(tokens.css 의 터치 기준). */
/* ★`color: inherit` 을 쓰지 말 것★ — 소개 홈페이지 첫 화면 띠는 글자색이 흰색이라
   흰 카드 위에서 제목이 통째로 사라진다(실측으로 한 번 그렇게 찍혔다). 이 줄 목록은
   어느 바탕 위에 서든 **흰 카드**이므로 글자색을 여기서 못 박는다. */
.offsite .off-list .li { min-height: 60px; padding: 12px 14px; text-decoration: none; color: var(--ink); }
.offsite .off-list .li .t { color: var(--ink); }
.offsite .off-list .li .s { color: var(--ink-2); line-height: 1.45; }
.offsite .off-list .li .avatar { background: var(--card-2); color: var(--ink-2); border-color: var(--line); }
/* 밖으로 나가는 표시. 글자(제목)가 이미 뜻을 지고 있고 이 그림은 거들 뿐이다. */
.offsite .off-go { flex: 0 0 auto; color: var(--ink-3); display: inline-flex; align-items: center; }
.offsite .off-go .i { width: 20px; height: 20px; }
.offsite .off-list .li:hover .off-go, .offsite .off-list .li:focus-visible .off-go { color: var(--ink); }

.offsite .off-note {
  margin: var(--s3) 0 0; font-size: var(--fs-sm); line-height: 1.6; color: var(--ink-2);
}
.offsite .off-note b { font-weight: 800; color: var(--ink); }
.offsite .off-note a { color: var(--ink); font-weight: 700; }

/* ---------------------------------------------------------- 숯색 머리판 위 (첫 화면)
 * 소개 홈페이지 첫 화면 띠 안에서는 글씨가 흰 계열이 된다. 카드(줄 목록)는 흰색을
 * 유지한다 — 숯색 위에 숯색을 얹으면 경계가 사라지고 대비를 지킬 방법이 없다
 * (`.duesbox.hero` 와 같은 판단). */
.offsite.hero > h2.mini { color: #fff; }
.offsite.hero .off-note { color: var(--bar-ink); }
.offsite.hero .off-note b, .offsite.hero .off-note a { color: #fff; }
.offsite.hero :focus-visible { outline: 3px solid #fff; outline-offset: 3px; }
@media (min-width: 1024px) {
  /* 대표님은 1920 모니터를 175% 로 보신다(= 실효 1097px). 첫 화면 안에서는 회비 칸과
     **같은 폭·같은 왼쪽 끝**으로 묶는다(위 `.duesbox.hero` 의 v1.60.0 정정 주석 참조:
     `.mast-in` 은 640px 이 아니라 1760px 까지 열려 있다). 실측 960x219@61 (1097px) ·
     960x219@160 (1920px), 제목 `h2.mini` 와 왼쪽 끝이 같다. */
  .offsite.hero .off-list, .offsite.hero .off-note { max-width: 960px; margin-left: 0; margin-right: auto; }
  .offsite.hero > h2.mini { max-width: 960px; margin-left: 0; margin-right: auto; }
}

/* 화면에는 안 보이고 낭독기만 읽는 글자. 「새 창으로 열림」처럼 그림만으로는 전할 수
   없는 말을 여기에 담는다. `display:none` 으로 바꾸지 말 것 — 낭독기도 같이 잃는다. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ==========================================================================
 * 홈 화면에 설치 (v1.45.0) — views/partials/install-app.ejs
 *
 * 대표님: 「옙 다운도 받게.」 스토어 등록 없이 홈 화면 아이콘으로 여신다.
 *
 * 줄 **하나**다. 정식 홈페이지 칸과 같은 `.list .li` 부품을 재사용하고 높이를 66px 로
 * 눌러 둔다. 종목 격자가 390px 첫 화면에서 한 픽셀도 밀리면 안 된다(대표님이
 * 「이화면 유지하면좋겠습니다」라고 지목하신 그 격자다). 배너로 키우지 말 것.
 *
 * 처음에는 `hidden` 이라 아무 자리도 안 먹는다. 설치가 실제로 가능한 기기에서만
 * public/js/pwa.js 가 연다.
 * ========================================================================== */
/* ★`hidden` 속성이 `.btn` 의 display 에 진다.★ 브라우저 기본 스타일시트의
   `[hidden] { display: none }` 은 우선순위가 제일 낮아서, `.btn { display: inline-flex }`
   같은 작성자 규칙이 있으면 **숨겨지지 않는다.** 실제로 아이폰 화면에서 [설치] 버튼이
   그대로 보였다(iOS 는 설치 이벤트가 없어 눌러도 아무 일이 없는 버튼이다 - 이 앱이
   금지하는 바로 그것). 이 저장소가 `.sportnow[hidden]` · `.rot-nav[hidden]` 에서 이미
   같은 방식으로 막고 있다. 이 줄을 지우지 말 것. */
.installbox [hidden] { display: none; }
.installbox { margin-top: var(--s5); }
.installbox .li { min-height: 66px; padding: 12px 14px; color: var(--ink); }
.installbox .li .t { color: var(--ink); }
.installbox .li .s { color: var(--ink-2); line-height: 1.45; }
.installbox .li .s.ios b { font-weight: 800; color: var(--ink); }
/* 인앱 브라우저 안내 세 줄 (v1.53.0). 숫자로 끊어 읽히게 `<ol>` 이다.
   대표님이 카카오톡 안에서 보고 계실 때 이 자리가 열린다. 여기에 버튼을 넣지 말 것 -
   인앱에서는 눌러도 아무 일이 안 일어난다(pwa.js 맨 위 주석). */
.installbox .li .s.inapp { margin: 6px 0 0; padding-left: 20px; }
.installbox .li .s.inapp li { margin-top: 3px; line-height: 1.5; word-break: keep-all; }
.installbox .li .s.inapp b { font-weight: 800; color: var(--ink); }
.installbox .li .avatar { background: var(--turf-bg); color: var(--turf-dark); border-color: var(--turf-line); }
/* 버튼은 줄 안에서만 자기 폭을 갖는다. block 으로 만들면 줄이 두 겹이 된다. */
.installbox .btn.install-go { flex: 0 0 auto; min-width: 84px; padding-left: var(--s4); padding-right: var(--s4); }

/* 소개 홈페이지 첫 화면 띠(숯색) 위. 줄 목록은 흰 카드를 유지하므로 안쪽 글자색은
   그대로 두고 바깥 여백만 맞춘다(`.offsite.hero` 와 같은 판단). */
.installbox.hero { margin-top: var(--s5); }
.installbox.hero :focus-visible { outline: 3px solid #fff; outline-offset: 3px; }
@media (min-width: 1024px) {
  /* 회비 칸·정식 홈페이지 칸과 같은 폭, 같은 왼쪽 끝 (v1.60.0). 실측 960x80@61 (1097px) ·
     960x80@160 (1920px). */
  .installbox.hero .list { max-width: 960px; margin-left: 0; margin-right: auto; }
  .installbox.hero > h2.mini { max-width: 960px; margin-left: 0; margin-right: auto; }
}

/* ==========================================================================
 * 야구 포지션 예약판 · 경기 댓글 · 실시간 예약현황판   (v1.48.0)
 *
 * 대표님이 2026-09-14 에 보내 주신 캡처를 그대로 만든 표다.
 *   타순 | 포지션(참가비) | 1루팀 | 3루팀   ← 네 칸
 *   칸 상태 셋: [신청하기] 초록 테두리 · 신청마감 회색 · 이름 초록 채움
 *
 * ★ 이 표는 **390px 에서 가로로 밀리지 않는다.** ★
 * 참고 서비스는 가로 스크롤을 쓰지만 이 클럽 회원은 어르신이고, 한 손으로 폰을 들고
 * 표를 옆으로 미는 동작은 이 앱이 전부 금지한 동작이다(NOTES 2장). 그래서
 * `table-layout: fixed` + 칸 폭 비율로 네 칸을 고정폭 안에 욱여넣는다.
 * **칸 폭(`.c-no`/`.c-pos`/`.c-team`)을 키우면 그 순간 가로 스크롤이 생긴다.**
 * 폰트도 여기서만 본문(17px)보다 작다 - 표 한 줄에 이름과 금액이 같이 들어가야 하고,
 * 대신 눌리는 부분(`.posbtn`)은 높이 44px 을 지켜 손가락 기준을 넘긴다.
 * ========================================================================== */
.posboard-card { padding: var(--s4); }
.posboard-lead { margin: 0 0 var(--s4); font-size: var(--fs-base); line-height: 1.5; }
/* 신청 마감 줄 (v1.50.0). 칸을 고르기 전에 "언제까지" 가 먼저 읽혀야 한다.
   본문보다 작게 쓰지 않는다 - 이 클럽 기준 글씨 하한이 17px 이다(NOTES 2장). */
.posboard-deadline {
  margin: 0 0 var(--s3); padding: 10px 12px; border-radius: var(--r-sm);
  background: var(--turf-bg); border: 1px solid var(--turf-line);
  font-size: var(--fs-base); line-height: 1.45; word-break: keep-all;
}

.posboard { width: 100%; border-collapse: collapse; table-layout: fixed; }
.posboard col.c-no { width: 11%; }
.posboard col.c-pos { width: 37%; }
.posboard col.c-team { width: 26%; }

.posboard th, .posboard td { border: 1px solid var(--line); padding: 8px 4px; text-align: center; vertical-align: middle; }
.posboard thead th {
  background: var(--card-2);
  font-size: var(--fs-sm);
  font-weight: 800;
  color: var(--ink-2);
  padding: 10px 2px;
}
/* 타순 숫자 칸. 좁으니 숫자만 크게 세운다. */
.posboard tbody th[scope="row"] { font-size: 19px; font-weight: 800; color: var(--ink); background: var(--card); }

.posboard td.pos { text-align: center; padding: 8px 5px; }
.posboard .pos-name { font-size: var(--fs-base); font-weight: 700; color: var(--ink); line-height: 1.3; word-break: keep-all; }
.posboard .pos-fee { margin-top: 2px; font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2); line-height: 1.3; }
/* 「(20,000P적립)」. 금액과 같은 줄에 두되 한 급 작게 - 내는 돈과 받는 포인트를
   같은 크기로 두면 어르신이 둘을 더해서 읽으신다. */
.posboard .pos-earn { display: block; font-size: var(--fs-xs); font-weight: 700; color: var(--turf-dark); }

.posboard td.cell { padding: 6px 4px; }
.posboard td.cell form { margin: 0; }

/* ① [신청하기] — 초록 테두리, 흰 바탕. 참고 화면 그대로. */
.posboard .posbtn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 44px;          /* 손가락 기준. 표가 좁아도 이 값은 안 줄인다. */
  padding: 6px 2px;
  border: 2px solid var(--turf);
  border-radius: 8px;
  background: #fff;
  color: var(--turf-dark);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 800;
  line-height: 1.2;
  cursor: pointer;
}
.posboard .posbtn:active { background: var(--turf-bg); }

/* ② 신청마감 — 회색 글자, 테두리 없음, 누를 것이 없다. 참고 화면의 흐린 글자다.
      **버튼처럼 보이게 만들지 말 것.** 눌리는 것처럼 생긴 것을 눌렀는데 아무 일도
      없으면 회원은 그것을 고장으로 읽는다. */
.posboard td.cell.closed { background: var(--card-2); }
.posboard td.cell.closed span { display: block; padding: 12px 0; font-size: var(--fs-sm); font-weight: 700; color: var(--ink-3); }

/* ③ 이름 — 초록 채움 + 흰 글씨. 이 칸이 "누가 가져갔다" 는 표시다. */
.posboard td.cell.taken { background: var(--turf); border-color: var(--turf); }
.posboard td.cell.taken .who {
  display: block;
  padding: 12px 2px;
  font-size: var(--fs-sm);
  font-weight: 800;
  color: #fff;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
/* 내 자리는 한 겹 더 진하게. 초록 칸이 여럿일 때 내 것을 눈으로 찾을 수 있어야 한다. */
.posboard td.cell.taken.mine { background: var(--turf-dark); box-shadow: inset 0 0 0 3px #fff, inset 0 0 0 6px var(--turf-dark); }

/* ④ 빈자리인데 지금 이분은 못 누르는 칸(비로그인 · 이미 다른 자리를 잡으심).
      비워 두면 "왜 나만 버튼이 없나" 가 되므로 글자로 상태를 적는다. */
.posboard td.cell.free span { display: block; padding: 12px 0; font-size: var(--fs-sm); font-weight: 700; color: var(--turf-dark); }

.posboard-foot { margin-top: var(--s4); }
.posboard-foot .hint { margin: 8px 0 0; font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-2); }
.posclear { display: flex; gap: 8px; flex-wrap: wrap; }
.posclear form { margin: 0; }

/* 데스크톱 폭에서는 칸이 남아돈다. 글씨를 본문 크기로 올려 준다(어르신 기준은
   작은 화면에서의 하한이지 큰 화면에서의 상한이 아니다). */
@media (min-width: 560px) {
  .posboard .posbtn, .posboard td.cell.closed span, .posboard td.cell.taken .who, .posboard td.cell.free span { font-size: var(--fs-base); }
  .posboard .pos-fee { font-size: var(--fs-base); }
  .posboard th, .posboard td { padding: 10px 6px; }
}

/* ---------------------------------------------------------------- 경기 댓글
 * 입력칸과 [등록] 이 **한 줄**이다(참고 화면 그대로). 두 줄로 벌리면 390px 에서
 * [등록] 이 화면 밖으로 밀린다. */
.cmt-card { padding: var(--s4); }
.cmt-list { list-style: none; margin: 0 0 var(--s4); padding: 0; }
.cmt { position: relative; padding: 10px 0; border-bottom: 1px solid var(--line-2); }
.cmt:last-child { border-bottom: 0; }
.cmt-head { display: flex; align-items: baseline; gap: 8px; }
.cmt-who { font-size: var(--fs-sm); font-weight: 800; color: var(--ink); }
.cmt-when { font-size: var(--fs-xs); color: var(--ink-3); }
.cmt-body { margin-top: 3px; font-size: var(--fs-base); line-height: 1.5; color: var(--ink); overflow-wrap: anywhere; }
.cmt-del { position: absolute; top: 8px; right: 0; margin: 0; }
.cmt-del button { background: none; border: 0; padding: 4px 2px; font: inherit; font-size: var(--fs-xs); color: var(--ink-3); text-decoration: underline; cursor: pointer; }
.cmt-empty { margin: 0 0 var(--s4); font-size: var(--fs-base); }
.cmt-form { display: flex; gap: 8px; margin: 0; }
.cmt-form input {
  flex: 1 1 auto;
  min-width: 0;                /* ★ 이 줄이 없으면 flex 항목이 안 줄어들어 [등록] 이 밖으로 나간다 ★ */
  height: 52px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  font-family: inherit;
  font-size: var(--fs-base);
  color: var(--ink);
}
.cmt-form button {
  flex: 0 0 auto;
  min-width: 84px;
  height: 52px;
  border: 2px solid var(--turf);
  border-radius: 8px;
  background: #fff;
  color: var(--turf-dark);
  font-family: inherit;
  font-size: var(--fs-base);
  font-weight: 800;
  cursor: pointer;
}
.cmt-form button:active { background: var(--turf-bg); }

/* ------------------------------------------- 종목별 실시간 예약현황판 (/status)
 * 숫자를 읽는 화면이다. 큰 숫자 하나 + 그 아래 설명 한 줄이 한 덩어리다. */
.stat-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: var(--s3); }
.stat-at { font-size: var(--fs-sm); color: var(--ink-2); }
.stat-at b { color: var(--ink); }
.stat-sport { margin-bottom: var(--s5); }
.stat-sum { display: flex; gap: 8px; margin: 0 0 var(--s3); }
.stat-sum .box { flex: 1 1 0; min-width: 0; padding: 12px 6px; border: 1px solid var(--line); border-radius: 10px; background: var(--card); text-align: center; }
.stat-sum .box .n { display: block; font-size: 26px; font-weight: 800; line-height: 1.1; color: var(--ink); }
.stat-sum .box .k { display: block; margin-top: 3px; font-size: var(--fs-sm); color: var(--ink-2); }
.stat-sum .box.open .n { color: var(--turf-dark); }
.stat-sum .box.full .n { color: var(--warn); }
.stat-row { display: block; padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--card); margin-bottom: 8px; text-decoration: none; color: inherit; }
/* v1.50.0: 줄이 「머리(링크) + 자리 칩」 두 층이 되었다. 머리는 예전 줄과 똑같이 보인다. */
.stat-row > .stat-go { display: block; text-decoration: none; color: inherit; }
.stat-row .t { font-size: var(--fs-base); font-weight: 700; color: var(--ink); line-height: 1.35; }
.stat-row .s { margin-top: 3px; font-size: var(--fs-sm); color: var(--ink-2); }
.stat-row .nums { margin-top: 6px; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; font-size: var(--fs-base); }
.stat-row .nums b { font-size: 20px; font-weight: 800; color: var(--turf-dark); }
.stat-row .nums .waitn { color: var(--hold); font-weight: 700; }
.stat-badge { display: inline-block; padding: 3px 9px; border-radius: 999px; font-size: var(--fs-xs); font-weight: 800; }
.stat-badge.OPEN { background: var(--turf-bg); color: var(--turf-dark); }
.stat-badge.FULL { background: var(--warn-bg); color: var(--warn); }
.stat-badge.CLOSED { background: var(--card-2); color: var(--ink-3); }
.stat-badge.CANCELLED { background: var(--card-2); color: var(--ink-3); }

/* 회원 홈 맨 위의 현황판 입구. 하단 탭은 5개가 전부라(NOTES 2장) 여기가 문이다. */
/* ------------------------------------------------- 현황판 자리 칩 (v1.50.0)
 *
 * 대표님: **「실시간예약하기 즉 예약안된건 보여주야됨」**. 숫자만 있던 줄 아래에
 * **자리 하나 = 칩 하나**가 붙는다. 빈자리 칩은 **누를 수 있는 것**이라 초록이고,
 * 예약된 칩은 회색 글자다(누를 것이 없으니 링크가 아니다).
 *
 * 누름 자리 44px 을 지킨다(이 앱 바닥은 48px 이지만 칩은 목록 안의 보조 동작이라
 * `.posbtn` 과 같은 44px 을 쓴다. 더 줄이지 말 것 - 어르신 손가락 기준이다).
 * 글씨는 15px 밑으로 내리지 않는다.
 */
.slotgrp { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--line-2); }
.slotgrp .gl { flex: 0 0 100%; font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2); margin-bottom: 2px; }
.slotgrp .gl b { color: var(--ink); }
.slotgrp .slot {
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 44px; padding: 7px 11px; border-radius: 8px;
  font-size: var(--fs-sm); font-weight: 700; line-height: 1.3; word-break: keep-all;
}
.slotgrp.taken .slot { background: var(--card-2); color: var(--ink-3); border: 1px solid var(--line); }
.slotgrp.taken .slot .st { font-weight: 800; }
/* 빈자리 = 이 화면에서 유일하게 누르는 것. 경기 카드의 초록과 같은 색을 쓴다. */
.slotgrp.open .slot.go {
  background: var(--turf-bg); color: var(--turf-dark); border: 1.5px solid var(--turf);
  text-decoration: none;
}
.slotgrp.open .slot.go:active { background: var(--turf-line); }
.slotgrp.open .slot.go .fee { font-weight: 800; }
.slotgrp.open .slot.go.wide { flex: 1 1 100%; justify-content: center; font-size: var(--fs-base); min-height: var(--tap); }
/* 20초 새로고침이 "방금 잡혔다" 고 바꾼 칩. 색만 회색으로 내린다(자리를 옮기지 않는다 -
   회원이 누르려던 순간 칩들이 줄줄이 움직이면 엉뚱한 칸을 누르시게 된다). */
.slotgrp .slot.justtaken { background: var(--card-2); color: var(--ink-3); border: 1px solid var(--line); }
.slotgrp.none .gl { margin-bottom: 0; color: var(--ink-3); }

/* ------------------------------------------- 현황판 **두 열짜리 자리표** (v1.53.0)
 *
 * 대표님이 손으로 그려 보내신 모양이다:
 *
 *     1루팀      3루팀
 *     포수       포수
 *     투수       투수
 *     ...
 *     지명타자   지명타자
 *
 * `table-layout: fixed` + `width: 100%` 라 **가로로 절대 넘치지 않는다.** 대표님
 * 휴대폰(540px)이 기준이고, 320px 까지도 칸이 줄어들 뿐 스크롤이 안 생긴다.
 * 여기에 `min-width` 나 고정 px 폭을 넣지 말 것 - 그 순간 가로 스크롤이 생긴다.
 *
 * 경기 화면의 `.posboard` 와 **일부러 닮게** 만들었다. 현황판에서 자리를 고르고
 * 경기 화면으로 넘어가면 같은 표가 나와야 회원이 "같은 자리" 라고 읽으신다.
 * 다만 여기는 타순 열이 없다(현황판은 "어느 자리가 남았나" 를 보는 화면이고,
 * 타순 번호는 신청 화면에서 보시면 된다. 열을 하나 줄여 이름 열을 넓힌다).
 */
.slotboard { margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--line-2); }
.slotboard > .gl { font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2); margin-bottom: 6px; }
.slotboard > .gl b { color: var(--turf-dark); }
.statboard { width: 100%; border-collapse: collapse; table-layout: fixed; }
/* 이름 열이 제일 넓다. 「지명타자1」 · 「알라 (오른쪽)」 이 한 줄에 들어가야 한다. */
.statboard col.c-pos { width: 40%; }
.statboard col.c-team { width: 30%; }
.statboard th, .statboard td { border: 1px solid var(--line); padding: 6px 4px; text-align: center; vertical-align: middle; }
.statboard thead th {
  background: var(--card-2); color: var(--ink-2);
  font-size: var(--fs-sm); font-weight: 800; line-height: 1.3;
  padding: 8px 4px; word-break: keep-all;
}
.statboard tbody th[scope="row"] {
  background: var(--card); color: var(--ink);
  font-size: var(--fs-sm); font-weight: 700; line-height: 1.3;
  text-align: left; padding-left: 8px; word-break: keep-all;
}
.statboard td.cell { padding: 4px 3px; }
/* 빈 칸 = 이 화면에서 유일하게 누르는 것. 경기 카드의 초록과 같은 색을 쓴다.
   누름 자리 44px 을 지킨다(어르신 손가락 기준. 더 줄이지 말 것). */
.statboard td.cell.free { background: var(--turf-bg); }
.statboard td.cell.free .go {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 44px; padding: 4px 2px; border-radius: 7px;
  color: var(--turf-dark); text-decoration: none;
  font-size: var(--fs-sm); font-weight: 800; line-height: 1.25;
}
.statboard td.cell.free .go:active { background: var(--turf-line); }
.statboard td.cell.free .go .fee { margin-top: 1px; font-size: var(--fs-xs); font-weight: 700; }
/* 예약된 칸 · 운영자가 닫아 둔 칸. 누를 것이 없으니 링크가 아니다. */
.statboard td.cell.taken { background: var(--card-2); }
.statboard td.cell.taken span,
.statboard td.cell.closed span {
  display: flex; align-items: center; justify-content: center;
  min-height: 44px;
  font-size: var(--fs-sm); font-weight: 700; color: var(--ink-3); line-height: 1.3;
}
.statboard td.cell.closed { background: var(--card-2); }
/* 20초 새로고침이 "방금 잡혔다" 고 바꾼 칸. 색만 회색으로 내린다(자리는 안 움직인다). */
.statboard .go.justtaken { color: var(--ink-3); font-weight: 700; }
.slotboard .bfoot { margin: 8px 0 0; font-size: var(--fs-sm); color: var(--ink-2); }
.slotboard .bfoot b { color: var(--ink); font-weight: 800; }

/* 좁은 화면(320~380px)에서는 글씨를 한 단계 줄여 「알라 (오른쪽)」 이 두 줄로
   접히더라도 칸이 안 깨지게 둔다. 가로 스크롤은 여기서도 안 생긴다. */
@media (max-width: 380px) {
  .statboard th, .statboard td { padding: 5px 2px; }
  .statboard tbody th[scope="row"] { padding-left: 5px; font-size: var(--fs-xs); }
  .statboard thead th { font-size: var(--fs-xs); }
}

/* 가맹점 신청 입구 (v1.50.0). 가맹점 상자 **바로 위** 한 줄.
   `.statlink`(초록 주 입구)와 일부러 다르게 흰 바탕 + 테두리다 - 이 화면의 주 동작은
   경기 신청이고 이 줄은 가게 사장님만 누르는 보조 입구다(NOTES 2장: 한 화면에 주 동작
   버튼 하나). 누름 자리는 그대로 48px 을 넘긴다. */
/* 표 칸의 둘째 줄 (v1.50.0). 회원 관리 표의 **주소**가 이 모양으로 이름 아래에 붙는다.
   열을 새로 만들지 않으려고 쓰는 자리라(1024px 여유가 18px 뿐이다), 여기에 긴 글을
   하나 더 얹지 말 것. 15px(--fs-sm)이 이 제품의 표 안 글씨 바닥이다. */
.cell-sub { display: block; margin-top: 3px; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); line-height: 1.4; word-break: keep-all; overflow-wrap: anywhere; }

.applylink {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 14px; margin: var(--s4) 0 var(--s2);
  border: 1.5px solid var(--line); border-radius: 12px; background: var(--card);
  color: var(--ink); text-decoration: none; min-height: var(--tap);
}
.applylink .t { display: block; font-size: var(--fs-base); font-weight: 800; line-height: 1.3; }
.applylink .s { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.4; word-break: keep-all; }
.applylink .go { flex: 0 0 auto; font-size: var(--fs-sm); font-weight: 800; color: var(--turf-dark); }
.applylink:active { background: var(--card-2); }

.statlink { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 14px 16px; margin-bottom: var(--s4); border: 2px solid var(--turf-line); border-radius: 12px; background: var(--turf-bg); color: var(--turf-dark); text-decoration: none; font-size: var(--fs-base); font-weight: 800; }
/* v1.65.0: 이 줄이 없었다. 화면의 **주 입구**인데 눌러도 아무 표시가 없었다(폰 포함).
   초록 테두리 색을 바탕으로 채운다. --turf-dark 글자가 이 바탕에서 6.4:1. */
.statlink:active { background: var(--turf-line); }
.statlink .go { font-size: var(--fs-sm); font-weight: 700; }

/* ------------------------------- 운영자: 야구 포지션 틀 편집 (/admin/batting-positions)
 * 한 줄이 한 덩어리다. 왼쪽에 타순 번호, 오른쪽에 이름 · 금액 두 칸 · 팀 체크 둘.
 * 390px 에서는 금액 두 칸이 나란히, 그보다 좁아지면 세로로 접힌다. */
.posedit { display: flex; gap: 10px; padding: 12px 0; border-bottom: 1px solid var(--line-2); }
.posedit:last-of-type { border-bottom: 0; }
.posedit-no {
  flex: 0 0 38px;
  height: 38px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 10px;
  background: var(--card-2);
  font-size: 18px; font-weight: 800; color: var(--ink-2);
}
.posedit-fields { flex: 1 1 auto; min-width: 0; }
.posedit-fields .f { margin-bottom: 8px; }
.posedit-money { display: flex; gap: 8px; }
.posedit-money .f { flex: 1 1 0; min-width: 0; }
.posedit-teams { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 4px; }

/* 체크박스. **글자까지 누르는 면적**이 되어야 한다(label 로 감싼 이유).
   어르신 손가락으로 16px 네모 하나만 노리게 두지 않는다. */
.chk { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; font-size: var(--fs-base); font-weight: 700; color: var(--ink); cursor: pointer; }
.chk input[type="checkbox"] { width: 24px; height: 24px; accent-color: var(--turf); flex: 0 0 auto; }

/* ==========================================================================
 * 대회 · 스코어 · 순위표   (v1.49.0)
 *
 * 대표님: 「파크를 중점으로 대회열었을때 스코아 스크린대회했을시 스코아 잘나와야됩니다」
 *
 * 이 구역에서 중요한 것은 두 가지다.
 *   1. **순위표는 줄(.list .li.rank)로 그린다.** 표로 만들지 않았다 - 순위·이름·조·
 *      입력 홀·합계 다섯 값을 390px 표에 넣으려면 글씨를 14px 아래로 내려야 하고,
 *      14px 이 이 제품의 절대 바닥이다(tokens.css). 줄이면 라벨과 숫자가 눈으로 이어진다.
 *   2. **점수 입력 칸은 손가락 기준을 지킨다.** 홀 칸은 56px 이고 한 줄에 셋이다.
 *      칸을 좁혀 한 줄에 여섯을 넣으면 그 순간 어르신이 옆 홀에 타수를 넣으신다.
 * ========================================================================== */

/* 대회 제목 아래의 딱지 줄(상태 · 점수 방식 · 홀 수 · 공개 여부). */
.tour-meta { display: flex; flex-wrap: wrap; gap: 6px; margin: var(--s2) 0 0; }

/* 1위 줄. **색을 금색으로 칠하지 않는다** - 이 저장소는 초록을 「누르는 것」 한 가지
   뜻으로만 쓰고, 장식용 색을 새로 들이면 그 규칙이 무너진다. 왼쪽 굵은 선 한 줄로
   1위를 표시한다(구역 머리의 세로선과 같은 방식이다). */
.list .li.tour-row.top { border-left: 6px solid var(--turf); }
.list .li.tour-row .amt { font-size: var(--fs-2xl); color: var(--ink); }
.list .li.tour-row .amt small { font-size: var(--fs-sm); }

/* ---------------------------------------------------------------- 홀 칸 (점수 입력)
 * 한 줄에 셋(390px). 칸마다 홀 번호가 **글자로** 붙는다 - 번호 없는 칸 열여덟 개를
 * 세로로 세우면 지금 몇 번째 홀인지 셈으로 알아내야 한다. */
.holegrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.holecell { display: block; }
.holecell .hn {
  display: block;
  margin-bottom: 4px;
  font-size: var(--fs-sm);
  font-weight: 800;
  color: var(--ink-2);
  text-align: center;
}
.holecell input {
  width: 100%;
  height: 56px;              /* 손가락 기준(48px)보다 넉넉히 위. 줄이지 말 것 */
  padding: 0 6px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-sm);
  background: #fff;
  font-family: inherit;
  font-size: 22px;           /* 숫자 한 자리를 눈으로 확인하는 칸이라 본문보다 크다 */
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--ink);
}
@media (min-width: 560px) {
  .holegrid { grid-template-columns: repeat(6, 1fr); }
}

/* ---------------------------------------------------------------- 홀별 점수 표
 * 9홀씩 끊어서 **가로 스크롤 없이** 넣는다. 18홀을 한 줄에 넣으면 390px 에서 밀린다.
 * 칸 폭을 키우면 그 순간 가로 스크롤이 생긴다(.posboard 와 같은 경고다). */
.holecard { margin-top: var(--s3); }
.holetable { width: 100%; border-collapse: collapse; table-layout: fixed; }
.holetable col.who { width: 26%; }
.holetable th, .holetable td {
  border: 1px solid var(--line);
  padding: 6px 1px;
  text-align: center;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}
.holetable thead th { background: var(--card-2); font-size: var(--fs-xs); font-weight: 800; color: var(--ink-2); }
.holetable th.who {
  width: 26%;
  text-align: left;
  padding-left: 6px;
  font-weight: 800;
  color: var(--ink);
  overflow-wrap: anywhere;   /* 긴 이름이 표를 가로로 밀지 않게 한다 */
}
.holetable tbody td { color: var(--ink); font-weight: 700; }

/* ---------------------------------------------------------------- 운영자 입력 보조
 * 회원 찾기 한 줄(칸 + [찾기])과 참가자 줄 안의 조 이름 칸. 둘 다 입력칸과 버튼이
 * **한 줄**이다. 두 줄로 벌리면 390px 에서 버튼이 화면 밖으로 밀린다. */
.tour-find { display: flex; gap: 8px; margin: 0 0 var(--s3); }
.tour-find input {
  flex: 1 1 auto;
  min-width: 0;              /* ★이 줄이 없으면 flex 항목이 안 줄어들어 버튼이 밖으로 나간다★ */
  height: var(--ctl-h);
  padding: 0 12px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-sm);
  background: #fff;
  font-family: inherit;
  font-size: var(--fs-control);
  color: var(--ink);
}
.tour-find button {
  flex: 0 0 auto;
  min-width: 88px;
  height: var(--ctl-h);
  border: 2px solid var(--turf);
  border-radius: var(--r-sm);
  background: #fff;
  color: var(--turf-dark);
  font-family: inherit;
  font-size: var(--fs-control);
  font-weight: 800;
  cursor: pointer;
}
.tour-find button:active { background: var(--turf-bg); }

.entrant-group { display: flex; gap: 8px; margin: 6px 0 0; }
.entrant-group input {
  flex: 1 1 auto;
  min-width: 0;
  height: var(--tap);
  padding: 0 10px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-sm);
  background: #fff;
  font-family: inherit;
  font-size: var(--fs-base);
  color: var(--ink);
}
.entrant-group .btn.sm { flex: 0 0 auto; }

/* ==========================================================================
 * 일정 달력 (v1.24.0 에 만들고 v1.60.0 에 여기로 옮김)
 * ==========================================================================
 *
 * 대표님이 보내신 클럽 공식 사이트 화면의 가운데에 있던 그 달력이다.
 * 값은 전부 `src/lib/calendar.ts` 가 만들고, 여기서는 생김새만 정한다.
 *
 * ---------------------------------------------------------------------------
 * v1.60.0: home.css 에서 여기로 통째로 옮겼다. 이유는 v1.31.0 의 `.certs` 와 똑같다.
 * ---------------------------------------------------------------------------
 *
 * 대표님 2026-09-21: 「날짜도 달력으로」. 그래서 같은 `views/partials/calendar.ejs` 를
 * **회원 앱 첫 화면**(`views/pages/matches.ejs`)에도 세웠다. 그런데 그 화면은
 * `pageCss` 를 넘기지 않아 home.css 를 아예 읽지 않는다. 규칙이 home.css 에 남아
 * 있으면 회원 화면의 달력은 표가 풀려 민짜로 그려진다. 두 화면이 다 읽는 파일은
 * 이것뿐이므로 여기로 옮긴다.
 *
 * **부품을 두 벌로 만들지 말 것.** `.member-cal` 을 새로 만드는 순간 한쪽만 고쳐지는
 * 날이 온다(NOTES 8d · 8ab · 8ai). 소개 홈페이지 생김새는 한 픽셀도 안 바뀐다 - 옮긴
 * 규칙과 맞서는 규칙이 home.css 에는 이제 하나도 없다.
 *
 * ---------------------------------------------------------------------------
 * ★ 제일 조심할 한 줄: **360px 에서 날짜 칸이 48px 밑으로 내려간다**
 * ---------------------------------------------------------------------------
 *
 * 소개 홈페이지(`.pub`, 좌우 여백 12~16px)에서는 안쪽 폭이 336px 이고 일곱 칸으로
 * 나누면 한 칸이 정확히 48.0px 다(실측: 표 352px, 한 칸 50.3px, 가로 스크롤 없음).
 * 회원 앱(`.wrap`, `--wrap-pad: 14px` · tokens.css)에서는 안쪽 폭이 332px 로 **더 좁다**
 * - 칸이 47.4px 로 떨어져 `scripts/ui-gate.js` 가 막는 48px 바닥을 깬다. 그래서 좁은
 * 폭에서 표를 좌우로 8px 씩 내미는 규칙이 이곳에서는 `.pub` 보다 더 필요하다(내밀면
 * 49.7px). 아래 `@media (max-width: 419px)` 규칙이 그것이고, 같은 이유로 `.wrap` 의
 * 좌우 여백(14px) 안에서 사라지지 않는다.
 * **이 규칙을 지우려면 먼저 360px 에서 한 칸 폭을 다시 재 볼 것.**
 */

.cal .cal-tabs { display: flex; gap: var(--s2); margin-top: var(--s4); }
.cal .cal-tab {
  flex: 1 1 0; min-height: var(--tap); padding: 0 var(--s3);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--line); border-radius: var(--r-sm); background: var(--card);
  color: var(--ink-2); font-size: var(--fs-md); font-weight: 800; white-space: nowrap;
}
/* 고른 칸은 색이 아니라 **채움**으로 알린다. 색만 다른 두 칸은 색을 못 보는 분에게 같다. */
.cal .cal-tab.on { background: var(--ink); border-color: var(--ink); color: #fff; }
/* v1.65.0: 고른 칸(`.on`)은 이미 숯색 채움이라 건드리지 않는다. 나머지 칸만. */
.cal .cal-tab:not(.on):active { background: var(--card-2); }

/* 달 넘기기. 화살표 기호가 아니라 한글이 적힌 버튼이고, 진짜 링크라 자바스크립트가
   죽어도 달이 넘어간다(이 저장소의 규칙: 아이콘만 있는 버튼을 만들지 않는다). */
.cal .cal-bar { display: flex; align-items: center; gap: var(--s2); margin-top: var(--s4); }
.cal .cal-mv {
  flex: 0 0 auto; min-height: var(--tap); padding: 0 var(--s3);
  display: inline-flex; align-items: center; white-space: nowrap;
  border: 1.5px solid var(--line); border-radius: var(--r-sm); background: var(--card);
  color: var(--ink); font-size: var(--fs-base); font-weight: 800;
}
.cal .cal-mv:active { background: var(--card-2); }
.cal .cal-mo {
  flex: 1; min-width: 0; margin: 0; text-align: center;
  font-size: var(--fs-xl); font-weight: 800; letter-spacing: -0.5px; line-height: var(--lh-tight);
}

.cal .cal-grid { width: 100%; margin-top: var(--s4); border-collapse: collapse; table-layout: fixed; }
.cal .cal-grid th {
  padding: 0 0 var(--s2); text-align: center;
  font-size: var(--fs-sm); font-weight: 800; color: var(--ink-2);
}
/* 일요일 6.95:1 · 토요일 7.90:1 (흰 바탕 실측). 달력에서 주말을 색으로 가르는 것은
   장식이 아니라 이 사용자층이 실제로 쓰는 표시다. 흐린 회색으로 낮추지 말 것. */
.cal .cal-grid th.sun { color: var(--warn); }
.cal .cal-grid th.sat { color: var(--sp-soccer); }
.cal .cal-grid td { padding: 0; border: 1px solid var(--line-2); vertical-align: top; }
.cal .cal-grid td.pad { background: var(--card-2); }
.cal .d {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  width: 100%; min-height: 56px; color: var(--ink); text-decoration: none;
}
.cal .d .n { font-size: var(--fs-base); font-weight: 700; line-height: 1; }
.cal .d.wd-0 .n { color: var(--warn); }
.cal .d.wd-6 .n { color: var(--sp-soccer); }
/* 일정이 있는 날 표시. 숫자만 굵게 바꾸면 "굵은 글씨" 로 읽히지 표시로 읽히지 않는다. */
.cal .d .mk { display: block; width: 20px; height: 5px; border-radius: 3px; background: var(--turf); }
.cal .d.has .n { font-weight: 800; }
.cal .d.has:active { background: var(--turf-bg); }
/* 지금 펼쳐 놓은 날. 테두리를 안쪽 그림자로 그려서 칸 폭(=누름 자리)이 줄지 않게 한다. */
.cal .d.on { background: var(--turf-bg); box-shadow: inset 0 0 0 2px var(--turf); }
.cal .d.today .n { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 2px; }

.cal .cal-days { margin-top: var(--s5); }
.cal .cal-day[hidden] { display: none; }
.cal .cal-dh {
  margin: 0 0 var(--s3); font-size: var(--fs-lg); font-weight: 800; letter-spacing: -0.3px;
  border-left: 5px solid var(--turf); padding-left: var(--s3);
}
.cal .cal-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); }
.cal .cal-ent {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  padding: var(--s3) var(--s4);
}
.cal .cal-ent .hd { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.cal .cal-ent .nm {
  margin: var(--s2) 0 0; font-size: var(--fs-md); font-weight: 800;
  line-height: 1.45; letter-spacing: -0.3px;
}
.cal .cal-ent .mt {
  margin: var(--s1) 0 0; display: flex; flex-wrap: wrap; gap: 0 var(--s3);
  font-size: var(--fs-base); line-height: 1.55; color: var(--ink-2);
}
.cal .cal-ent .mt .tm { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }
.cal .cal-ent .more {
  display: inline-flex; align-items: center; min-height: var(--tap); margin-top: var(--s1);
  font-size: var(--fs-base); font-weight: 700; color: var(--turf-dark);
  text-decoration: underline; text-underline-offset: 4px;
}
/* 일정이 없는 달. 격자는 그대로 서 있고 이 한 줄만 붙는다(빈 화면을 주지 않는다). */
.cal .cal-empty {
  margin: var(--s5) 0 0; padding: var(--s5) var(--s4); text-align: center;
  background: var(--card-2); border: 1px solid var(--line); border-radius: var(--r-sm);
  font-size: var(--fs-base); font-weight: 700; color: var(--ink-2);
}
.cal > .sub-actions { margin-top: var(--s4); }

/* 좁은 폭에서 달력 표를 좌우로 조금 내민다. 이유는 위 "일정 달력" 칸의 첫 문단에 있다
   (한 칸이 48px 밑으로 내려가는 것을 막는다). `.pub` 좌우 여백이 12~16px 이므로
   내밀어도 화면 밖으로 나가지 않는다. */
@media (max-width: 419px) {
  .cal .cal-grid { width: calc(100% + 16px); margin-left: -8px; }
}

/* ---------------------------------------------------------------------------
 * 회원 앱에서의 구역 머리. 소개 홈페이지는 home.css 의 `.sect > h2`(48px) 와
 * `.sect > .say` 가 나중에 읽혀 이 규칙을 덮는다(특이도가 같아서 나중 파일이 이긴다).
 * 여기 값은 회원 앱의 다른 구역 머리(`.certs > h2`, v1.31.0)와 같은 규칙이다.
 * ------------------------------------------------------------------------- */
.cal { margin-top: 26px; }
.cal > h2 {
  font-size: var(--fs-lg); font-weight: 800; line-height: var(--lh-tight);
  border-left: 4px solid var(--ink); padding-left: 10px;
}
.cal > .say { margin: var(--s3) 0 0; font-size: var(--fs-sm); line-height: 1.6; color: var(--ink-2); }


/* ==========================================================================
 * v1.50.0 — 화면을 세로로 줄인다. 대표님: **「그리고 전체적으로 줄여야될듯하고」**
 * ==========================================================================
 *
 * 대표님이 보시는 폭은 390px 이다. 고치기 전 실측(스테이징, 운영 DB 복사본, 회원 홈):
 *
 *     문서 전체 5,608px · 종목 격자 453 · 쿠폰 534 · 회비 463 · 정식 홈페이지 403
 *     경기 카드 287 x 4 = 1,150 · 푸터 1,118(협력 기관 띠 508)
 *
 * **글씨는 한 px 도 줄이지 않았다.** 이 클럽은 어르신 기준이라 본문 17px · 버튼 글씨
 * 18~19px 이 요구사항이다(NOTES 2장). 줄인 것은 **여백 · 그림 높이 · 칸 사이 간격**뿐이고,
 * 누름 자리 48px 바닥도 그대로다. `scripts/ui-gate.js` 가 그 셋을 전부 다시 잰다.
 *
 * 여기 값을 더 줄이려는 다음 사람에게: **글씨 크기와 --tap 은 건드리지 말 것.**
 * 그건 줄이는 것이 아니라 이 클럽 회원이 못 읽게 만드는 것이다. 줄일 것이 더 필요하면
 * 칸을 없애는 쪽(대표님과 상의)이지 글씨를 줄이는 쪽이 아니다.
 *
 * 이 블록은 파일 **맨 끝**에 있어야 위의 기본값들을 덮는다. 위로 옮기지 말 것.
 */

/* 종목 격자. 그림 66 → 54, 칸 높이 128 → 108. 그림이 아직 "무엇인지" 는 알아볼 수 있다
   (v1.12.0 이 손그림 → 실사로 키운 취지는 살아 있다). 두 칸 격자는 그대로다. */
.sportpick { padding: 9px; gap: 7px; }
.sportpick .sp-grid { gap: 7px; }
.sportpick .sp-tile { min-height: 108px; padding: 7px 8px 9px; gap: 5px; }
.sportpick .pic { height: 54px; }
.sportpick .sp-all { min-height: 52px; padding: 8px 12px; }

/* 경기 카드 = **예약창의 알맹이**. 카드 하나에서 30px 안팎을 벌면 네 건이면 한 화면이다.
   글씨·배지·게이지는 그대로고 줄어든 것은 안쪽 여백과 줄 간격이다. */
.stack { gap: 10px; }
.match-card { padding: var(--s3) var(--s3) var(--s3) var(--s4); }
.match-card .row1 { margin-bottom: 4px; }
.match-card .ttl { margin-bottom: 4px; }
.match-card .meta { gap: 3px; }
.match-card .foot { margin-top: 8px; padding-top: 8px; }

/* 안내 칸 넷(설치 · 정식 홈페이지 · 쿠폰 · 회비). 칸 사이가 24~32px 이던 것을 16px 로 모은다. */
.installbox { margin-top: var(--s4); }
.offsite { margin-top: var(--s4); }
.offsite .off-list .li { min-height: 56px; padding: 10px 14px; }
.coupons { margin-top: var(--s4); }
.coupon { padding: var(--s3); }
.coupon .shot { --nh: 104px; }
.coupon .tx { margin-top: var(--s2); }
.duesbox { margin-top: var(--s4); }
.dues-cards { margin-top: var(--s3); }
.dues-card { padding: var(--s3); }

/* 구역 제목 위 여백. 24px x 여섯 구역이면 그것만으로 한 화면의 6분의 1이다. */
.sect-head { margin: var(--s4) 2px var(--s2); }
.certs { margin-top: var(--s4); }
.rotbox { margin-top: var(--s4); }

/* 푸터의 협력 기관 띠. v1.62.0 에서 칸 폭 상한(230px) 줄을 **지웠다.** 그 줄은 한 판에
   기관 한 곳만 서던 때, 폰에서 판 하나가 330px 짜리 정사각이라 한 화면의 40% 를 먹던
   것을 막으려고 씌운 상한이었다. 이제 한 판이 가로 3칸이라 칸 폭이 애초에 판의 1/3
   (폰에서 약 110px)이고, 상한을 다시 씌우면 오히려 칸이 열 폭보다 좁아져 격자에
   빈틈이 생긴다. 다시 적지 말 것. */
.afflstrip { margin-bottom: var(--s4); padding-bottom: var(--s4); }

/* 종목 타일의 실사 사진. 4:3 비율은 그대로 두고 폭 상한만 160 → 132 로 내린다
   (비율을 건드리면 v1.15.0 이 "사진이 잘리지 않게" 맞춰 둔 계산이 깨진다). */
.sportpick .pic.photo { max-width: 132px; }

/* 정식 홈페이지 · 설치 줄. 한 줄이 96px 이던 것을 84px 로. 글씨는 그대로다. */
.offsite .off-list .li, .installbox .li { padding: 9px 14px; }
.offsite .off-note { margin-top: var(--s2); }

/* 쿠폰 카드의 글 칸. 줄과 줄 사이만 좁힌다(글씨 크기 · [쿠폰 받기] 버튼 높이는 그대로). */
.coupon .ln { margin-top: 5px; line-height: 1.45; }
.coupon .btn.dl { margin-top: var(--s3); }

/* 회비 두 칸. 카드 안쪽 여백과 줄 간격만. 금액 글씨는 그대로 크게 둔다(v1.46.0). */
.duesbox > .say { margin-top: var(--s2); line-height: 1.45; }
.duesbox .dues-note { margin-top: var(--s3); line-height: 1.45; }

/* 푸터. 블록 사이 20px → 12px, 아래 링크 줄 위 여백 20/16 → 12/12.
   글씨 크기(15px)와 링크 누름 자리(48px)는 그대로다. */
.sitefoot .foot-top { gap: var(--s3); }
.sitefoot .bizinfo .bi { line-height: 1.55; }
.sitefoot .legal { margin-top: var(--s3); padding-top: var(--s3); }

/* 넓은 화면(v1.21.0)의 달력 규칙. home.css 의 768px 칸에서 여기로 옮겼다(v1.60.0).
   칸이 넓어졌으니 높이도 같이 올린다(폰 56px → 76px), 칸 안의 목록은 여러 단이 된다.
   소개 홈페이지와 회원 앱이 같은 값을 쓴다(부품은 한 벌이다). */
@media (min-width: 768px) {
  .cal .d { min-height: 76px; }
  .cal .cal-tab { flex: 0 0 auto; min-width: 180px; }
  .cal .cal-list { grid-template-columns: 1fr 1fr; gap: var(--s4); }
}
@media (min-width: 1024px) {
  .cal .cal-list { grid-template-columns: 1fr 1fr 1fr; }
}

/* ==========================================================================
 * 마우스를 올렸을 때 (v1.65.0)
 * ==========================================================================
 *
 * 왜 이 절이 생겼나: v1.64.0 까지 이 저장소에는 **눌린 표시(`:active`)가 38곳**
 * (app.css 33 · home.css 5) 있는데 **올린 표시(`:hover`)는 세 곳**(`.kakao-action` ·
 * `.offsite .off-list .li` · home.css 의 넓은 화면 메뉴)뿐이었다. 폰만 보고 만든
 * 결과다. 대표님은 1920 모니터를 175% 로 보시고(= 실효 1097px) 마우스로 쓰시는데,
 * 그 화면에서는 링크 위에 커서를 올려도 아무 일도 일어나지 않아 「눌러도 되는
 * 것인지」가 화면에 안 적혀 있었다.
 *
 * 세는 중에 **눌린 표시조차 없던 네 곳**이 나왔다(폰에서도 무반응이었다는 뜻이다).
 * 올린 표시만 넣으면 폰 사용자는 그대로 방치되므로, 네 곳은 제 위치에 `:active` 를
 * 먼저 넣고 여기서 같은 색으로 짝을 맞췄다: `.statlink`(:3734 부근, 대회·상태 화면의
 * 주 입구) · `.pager a`(:1203) · `.cal .cal-tab:not(.on)`(:3928) ·
 * `a.match-card`(:524).
 *
 * 규칙 셋. 어기지 말 것.
 *
 *   ① **`@media (hover: hover) and (pointer: fine)` 안에만 적는다.** 이 칸이 없으면
 *      폰에서 손가락을 뗀 뒤에도 올린 색이 남는다(터치 브라우저가 :hover 를 붙잡고
 *      있는다). 어르신이 "눌렀는데 색이 안 돌아온다" 를 고장으로 읽는 자리다.
 *   ② **색과 테두리만 바꾼다.** 변형(transform)·전환(transition)·그림자 금지. 이 저장소에
 *      `transition` 은 한 줄도 없다(`.btn:active` 의 「되돌리지 말 것」 주석과 같은 이유:
 *      손이 떨리는 회원에게 1px 움직임은 빗나감으로 읽힌다). 새 색을 만들지 않는다 -
 *      **각 요소의 `:active` 색을 그대로 가져온다.** 그래서 마우스로 올린 색과 눌린 색이
 *      같고, 배울 것이 하나뿐이다.
 *   ③ **못 누르는 것에는 안 붙는다.** `:not(:disabled)` 가 그것이다. `.btn:disabled` 의
 *      `opacity: .62` 가 「지금은 안 됩니다」 를 말하고 있는데 커서를 올려 색이 바뀌면
 *      그 말이 거짓이 된다.
 *
 * 안 넣은 것(일부러):
 *   - `table.tbl tbody tr` (운영자 표. 줄이 링크가 아니다. `:active` 는 옛날 흔적이다)
 *   - `div.mainspon`(main-sponsor.ejs) · `div.sponsor.affl`(affiliations.ejs) ·
 *     install-app 의 `div.li` · admin-batting-positions 의 `span.posbtn`
 *     → 같은 클래스의 **링크 아닌** 변종이다. 그래서 아래는 `a.mainspon` · `a.sponsor` ·
 *       `button.posbtn` 처럼 태그를 붙여 적는다. 붙이지 않으면 못 누르는 미리보기 칸이
 *       눌리는 것처럼 보인다(이 저장소가 금지하는 바로 그것).
 *   - `.pager span.cur` · `.pager span.mute` · `calendar.ejs` 의 `span.d`(일정 없는 날) ·
 *     `.slotgrp .slot.justtaken` · `.posboard td.cell.closed` → 누를 것이 없는 자리.
 *   - `.offsite .off-list .li:hover` 는 위(v1.45.0)에 이미 있다. 화살표 색만 바뀌는 것이
 *     그 줄의 의도다(줄 목록 전체가 흰 카드를 유지해야 한다). 여기서 덧칠하지 말 것.
 * ========================================================================== */
@media (hover: hover) and (pointer: fine) {
  /* 버튼 (`:active` 색 그대로) */
  .btn:not(:disabled):hover { background: var(--card-2); }
  .btn.primary:not(:disabled):hover { background: var(--turf-dark); border-color: var(--turf-dark); }
  .btn.ghost:not(:disabled):hover { background: var(--card-2); }
  .btn.danger:not(:disabled):hover { background: var(--warn-bg); }

  /* 한 화면에 하나뿐인 주 동작 */
  .hero-action:not(:disabled):hover { background: var(--turf-dark); }
  .hero-action.warn:not(:disabled):hover { background: var(--warn-bg); }
  .hero-action.earn:not(:disabled):hover { background: var(--turf-dark); }
  .hero-action.spend:not(:disabled):hover { background: #5f3d04; }

  /* 머리띠 안의 아이콘 버튼 · 카톡 심볼 버튼 */
  .appbar .icon-btn:not(:disabled):hover { background: var(--bar-line); }
  .appbar .kakao-logo-btn:not(:disabled):hover { background: #f4dc00; }

  /* 카드·줄 전체가 누르는 자리인 것들 */
  a.match-card:hover { border-color: var(--ink-3); }
  a.sponsor:hover { background: var(--card-2); }
  a.mainspon:hover { background: var(--card-2); }
  .mk-item:hover { background: var(--card-2); }
  .entry-row:hover { background: var(--card-2); }
  .applylink:hover { background: var(--card-2); }
  .team-entry:hover { background: var(--turf-bg); }
  .notice-docs > li > a:hover { background: var(--line-2); }
  .afflstrip a.affl:hover .mk { background: var(--line-2); }
  .pic-tile:hover img { border-color: var(--ink-3); background: var(--line-2); }
  .certs .cert:hover .pic { background: var(--card-2); border-color: var(--ink-3); }

  /* 초록 입구(빈자리 · 상태표 칸 · 대회 주 입구). 이 화면들에서 유일하게 누르는 것이다. */
  .slotgrp.open .slot.go:hover { background: var(--turf-line); }
  .statboard td.cell.free .go:hover { background: var(--turf-line); }
  .statlink:hover { background: var(--turf-line); }

  /* 목록·달력·페이지 넘기기 */
  .pager a:hover { background: var(--card-2); }
  .cal .cal-mv:hover { background: var(--card-2); }
  .cal .d.has:hover { background: var(--turf-bg); }
  .cal .cal-tab:not(.on):hover { background: var(--card-2); }

  /* 넣는 화면의 조작부 */
  .stepper .step:not(:disabled):hover { background: var(--line-2); border-color: var(--ink-3); }
  .rotbox .rot-btn:not(:disabled):hover { background: var(--card-2); }
  .cmt-form button:not(:disabled):hover { background: var(--turf-bg); }
  .tour-find button:not(:disabled):hover { background: var(--turf-bg); }
  .posboard button.posbtn:not(:disabled):hover { background: var(--turf-bg); }
  .dir.earn:not(:disabled):hover { background: #d5e8dd; }
  .dir.spend:not(:disabled):hover { background: #f7e8cd; }

  /* 글자 링크. 바탕이 없는 자리라 색만 한 단계 진해진다(--ink-2 → --ink, 흰 바탕 12.6:1). */
  .sect-head a.more:hover { color: var(--ink); }
  .sub-actions a:hover, .sub-actions button:not(:disabled):hover { color: var(--ink); }
  /* 숯색 머리판 위에서는 --ink(거의 검정)가 사라진다. 흰색으로 올린다. 위 규칙보다
     칸 수가 하나 더 많아 이긴다(`.duesbox.hero .sub-actions a` 와 같은 자리). */
  .duesbox.hero .sub-actions a:hover { color: #fff; }
}

/* ==========================================================================
 * v1.67.0 mobile design pass (phones, 480px and below).
 * The customer (대표님) checks on an Android phone about 390px wide. Every
 * earlier visual pass was scoped to 1024px and up, so nothing reached him.
 * Structure, routes and brand colors stay; this block only restyles.
 * Rules that must hold here: body text >= 17px (14px floor), tap >= 48px,
 * header stays charcoal, green = pressable, 1px card lines, no transforms,
 * sponsor slide widths untouched (rotator.js measures them).
 * ========================================================================== */

/* Global fix (all widths): the visually hidden fold checkbox is absolutely
   positioned. Inside a folded `.secfold-body` it had no positioned ancestor,
   so it escaped to the page and stretched the document ~470px below the
   footer (the gray gap under the dark footer on phones). */
.secfold .secfold-body { position: relative; }
.fold-t, .secfold-t { clip: rect(0 0 0 0); left: 0; top: 0; pointer-events: none; }

/* v1.70.0 docx item 08 「접기·펼치기와 관련하여 화면이 위로 올라가는 현상」.
   Measured on prod 2026-09-28 (430px): the intro-text checkbox sat at page Y=0
   (offsetParent BODY) while its [더보기] label was at Y=4357. Tapping a label focuses
   its checkbox and the browser scrolls the focused element into view, so the page
   jumped to the very top. Anchor each checkbox inside its own fold, right where its
   label is: `.fold` label is at the bottom, `.secfold` head is at the top. */
.fold, .secfold { position: relative; }
.fold > .fold-t { top: auto; bottom: 0; }
.secfold > .secfold-t { top: 0; }

@media (max-width: 480px) {
  /* ---- top header ---- */
  .appbar {
    gap: 10px;
    box-shadow: inset 0 -3px 0 var(--turf), 0 2px 10px rgba(0, 0, 0, .18);
  }
  .appbar .brand .mark { border-radius: 9px; box-shadow: 0 0 0 2px rgba(255, 255, 255, .12); }
  .appbar .kakao-logo-btn { border-radius: 12px; }
  .appbar .who { font-size: var(--fs-xs); color: #b9c2cc; }
  .appbar .who b { font-size: var(--fs-base); font-weight: 800; letter-spacing: -0.2px; }
  .appbar .icon-btn, .appbar .back { border-radius: 12px; }
  .appbar .icon-btn { background: transparent; border-color: #4a535e; font-size: var(--fs-sm); }

  /* ---- sticky sport bar ---- */
  .sportnow {
    padding: 8px var(--wrap-pad);
    border-bottom: 0;
    box-shadow: 0 1px 0 var(--line), 0 6px 14px rgba(20, 24, 29, .08);
  }
  .sportnow .sn-btn { border-radius: 999px; background: var(--card-2); padding-left: 16px; padding-right: 14px; }

  /* ---- main sponsor ---- */
  .mainspon-wrap { padding: 14px var(--wrap-pad) 0; }
  .mainspon-wrap.rotbox { padding-bottom: 14px; }
  .mainspon { border-radius: 16px; border-color: var(--line); overflow: hidden; }
  .mainspon .tx { padding: 12px 16px 14px; }
  .mainspon .nm { letter-spacing: -0.3px; }

  /* rotator prev/next: pills with a centered counter */
  .rot-nav { gap: 8px; align-items: center; }
  .rot-btn { border-radius: 999px; border-width: 1px; background: var(--card-2); font-size: var(--fs-base); }
  .rot-count { min-width: 64px; text-align: center; font-size: var(--fs-sm); color: var(--ink-3); font-weight: 700; }
  .rot-count b { color: var(--ink); font-size: var(--fs-base); }

  /* ---- sport picker ---- */
  .sportpick { padding: 14px var(--wrap-pad) 16px; gap: 10px; border-bottom: 0; box-shadow: 0 1px 0 var(--line); }
  .sportpick .sp-grid { gap: 10px; }
  .sportpick .sp-all, .sportpick .sp-tile { border-radius: 14px; border-width: 1px; background: var(--card); }
  .sportpick .sp-all { background: var(--card-2); letter-spacing: -0.2px; }
  .sportpick .sp-tile { box-shadow: 0 1px 2px rgba(20, 24, 29, .05); }
  .sportpick .sp-all.on { background: var(--ink); border-color: var(--ink); color: #fff; }

  /* ---- section headings: turf accent pill instead of the ink border ----
     The 4px ink border-left showed as stray black squares at the left edge
     when the heading slid under the sticky sport bar. */
  .sect-head h2, .rotbox > .cap h2, .cal > h2, .certs > h2, .coupons > h2, .duesbox > h2, .clubintro h2 {
    border-left: 0; padding-left: 0; display: flex; align-items: center; gap: 10px; letter-spacing: -0.3px;
  }
  .sect-head h2::before, .rotbox > .cap h2::before, .cal > h2::before,
  .certs > h2::before, .coupons > h2::before, .duesbox > h2::before, .clubintro h2::before {
    content: ""; flex: 0 0 auto; width: 5px; height: 20px; border-radius: 3px; background: var(--turf);
  }
  .afflstrip .cap h2::before { background: #6fd3a4; }
  .rotbox > .cap .sub, .cal > .say { color: var(--ink-3); }

  .page-title { letter-spacing: -0.5px; }

  /* ---- notices ---- */
  .notice-list { gap: 12px; }
  .notice {
    border-radius: 16px; padding: 16px 16px 10px; border-color: var(--line-2);
    box-shadow: 0 1px 3px rgba(20, 24, 29, .06);
  }
  .notice .hd { align-items: center; }
  .notice .hd h3 { letter-spacing: -0.3px; line-height: 1.35; }
  .notice .hd .badge { border-radius: 999px; }
  .notice .dt {
    display: inline-flex; align-items: center; margin-top: 8px; padding: 3px 10px;
    border-radius: 999px; background: var(--card-2); color: var(--ink-2); font-size: var(--fs-xs);
  }
  .notice p { color: var(--ink); }

  /* ---- big green reserve button + stat link ---- */
  .hero-action { border-radius: 16px; box-shadow: 0 6px 16px rgba(10, 96, 64, .22); letter-spacing: -0.3px; }
  .statlink { border-radius: 16px; border-width: 1px; padding: 14px 16px; min-height: 60px; }
  .statlink .go {
    padding: 5px 12px; border-radius: 999px; background: var(--card); border: 1px solid var(--turf-line);
    white-space: nowrap;
  }

  /* ---- calendar ---- */
  .cal .cal-tabs { gap: 6px; padding: 4px; background: var(--card-2); border: 1px solid var(--line); border-radius: 999px; }
  .cal .cal-tab { border: 0; border-radius: 999px; background: transparent; }
  .cal .cal-tab.on { background: var(--ink); color: #fff; box-shadow: 0 2px 6px rgba(20, 24, 29, .2); }
  .cal .cal-mv { border-radius: 999px; border-width: 1px; background: var(--card); min-width: 72px; }
  .cal .cal-mo { letter-spacing: -0.4px; }
  .cal .cal-grid { border-collapse: separate; border-spacing: 3px; }
  .cal .cal-grid td { border: 0; border-radius: 10px; background: var(--card); }
  .cal .cal-grid td.pad { background: transparent; }
  .cal .cal-grid .d { border-radius: 10px; }
  .cal .cal-grid .mk { width: 8px; height: 8px; border-radius: 50%; }
  .cal .cal-grid .d.on { box-shadow: inset 0 0 0 2px var(--turf); }
  .cal .cal-dh {
    border-left: 0; padding: 0; display: flex; align-items: center; gap: 10px; letter-spacing: -0.3px;
  }
  .cal .cal-dh::before { content: ""; width: 5px; height: 20px; border-radius: 3px; background: var(--turf); flex: 0 0 auto; }
  .cal .cal-ent { border-radius: 16px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(20, 24, 29, .06); }
  .cal .cal-ent .badge { border-radius: 999px; }

  /* ---- match list scope tabs: one line, segmented ---- */
  .scopepick {
    gap: 4px; padding: 4px; background: var(--card-2); border: 1px solid var(--line); border-radius: 999px;
  }
  .scopepick a {
    min-height: var(--tap); border: 0; border-radius: 999px; background: transparent;
    font-size: var(--fs-sm); white-space: nowrap; padding: 0 6px; letter-spacing: -0.3px;
  }
  .scopepick a br { display: none; }
  .scopepick a.on { background: var(--ink); color: #fff; box-shadow: 0 2px 6px rgba(20, 24, 29, .2); }

  /* ---- match card ---- */
  .match-card {
    border-radius: 16px; border-color: var(--line-2); padding: 16px 16px 14px 20px;
    box-shadow: 0 1px 3px rgba(20, 24, 29, .06);
  }
  .match-card .row1 { margin-bottom: 10px; }
  .match-card .row1 .badge { border-radius: 999px; }
  .match-card .pill-time { margin-left: auto; padding: 4px 10px; border-radius: 999px; background: var(--card-2); }
  .match-card .ttl { font-size: 22px; letter-spacing: -0.4px; margin-bottom: 10px; }
  .match-card .meta { gap: 6px; }
  .match-card .foot { margin-top: 14px; padding-top: 12px; gap: 12px; }
  .match-card .gauge { height: 10px; border-radius: 999px; border: 0; background: var(--line-2); overflow: hidden; }
  .match-card .gauge i { border-radius: 999px; }

  /* ---- install / share cards ---- */
  .installbox .list { border-radius: 16px; border-color: var(--line-2); }
  .installbox .li { gap: 12px; padding: 12px 14px; }
  .installbox .li .avatar { width: 44px; height: 44px; flex: 0 0 44px; border-radius: 12px; }
  .installbox .li .t { font-size: var(--fs-base); font-weight: 800; word-break: keep-all; line-height: 1.3; }
  .installbox .li .s { word-break: keep-all; }
  .installbox .btn.install-go, .sharebox .btn.primary {
    min-width: 72px; padding-left: var(--s3); padding-right: var(--s3); border-radius: 12px;
  }
  .sharebox .li .avatar { background: #fee500; color: #191919; border-color: #f0d800; }

  /* ---- sponsors / affiliations: a short list sits on one centered row
     instead of 2 + 1 hanging off-center. Slide widths are unchanged. */
  .sponsors .rot-track:not(:has(.rot-slide:nth-child(4))),
  .afflstrip .rot-track:not(:has(.rot-slide:nth-child(4))) {
    grid-template-rows: auto; justify-content: center;
  }
  .sponsors .sponsor { border-radius: 14px; border-color: var(--line-2); }

  /* ---- 후원사 · 클럽 안내 fold ---- */
  .secfold-head { border-radius: 14px; background: var(--card); border-color: var(--line); min-height: 64px; }
  .secfold-head .t { letter-spacing: -0.3px; }

  /* ---- footer: ends cleanly just above the tab bar ---- */
  .sitefoot { padding-top: var(--s6); }
  .sitefoot:not(.flat) {
    margin-bottom: calc(-1 * (var(--tab-h) + 28px + env(safe-area-inset-bottom)));
    padding-bottom: calc(var(--tab-h) + var(--s5) + env(safe-area-inset-bottom));
  }
  .sitefoot .org { letter-spacing: -0.3px; }
  .sitefoot .relsites h2 { color: #9aa4af; }
  .afflstrip .affl .mk { border-radius: 12px; }

  /* ---- bottom tab bar: active item gets a turf pill behind the icon ---- */
  .tabbar { border-top: 0; box-shadow: 0 -1px 0 var(--line), 0 -6px 18px rgba(20, 24, 29, .08); }
  .tabbar a { gap: 3px; font-size: var(--fs-sm); color: var(--ink-2); }
  .tabbar a .i { width: 56px; height: 30px; padding: 3px 16px; border-radius: 999px; box-sizing: border-box; }
  .tabbar a.on { box-shadow: none; color: var(--turf-dark); font-weight: 800; }
  .tabbar a.on .i { background: var(--turf-bg); }
}
@media (max-width: 379px) {
  .scopepick a { font-size: var(--fs-xs); padding: 0 4px; letter-spacing: -0.4px; }
  .cal .cal-grid { border-spacing: 1px; }
}

/* ------------------------------------------ notices as a horizontal swipe row (v1.68.0)
 *
 * 대표님: 「공지상황도 가로로 부탁했습니다」 (2026-09-24). v1.64.0 put notices side by side
 * only from 540px, so on his ~390px phone they were still stacked one per row.
 *
 * At <=480 the list becomes ONE row that you swipe sideways (native scroll-snap, the
 * same mechanism as `.rotbox .rot-view`, no JS drag). Each card is 82% of the row so the
 * next card peeks in from the right: that peek is the "there is more, swipe" hint.
 * A 2-column grid was tried at 390 and rejected: 175px columns wrap the 20px titles
 * into 3 lines and the block got taller, not shorter.
 *
 * Same markup on the home block and on /notices (notice-article.ejs is untouched).
 * Photo thumbnails keep the .pic-tile rules (150px, contain, 크게 보기, opens the original).
 * Must stay AFTER the v1.67.0 <=480 block above (same specificity, later wins).
 */
@media (max-width: 480px) {
  .notice-list {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 82%;
    align-items: stretch;
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: none; -ms-overflow-style: none;
    /* room for the card shadow so it is not clipped by the scroll box */
    padding: 2px 2px 8px;
    scroll-padding-left: 2px;
  }
  .notice-list::-webkit-scrollbar { display: none; }
  .notice-list > .notice { scroll-snap-align: start; min-width: 0; }
  /* one notice only: no row, full width as before */
  .notice-list:not(:has(> .notice:nth-child(2))) { grid-auto-columns: 100%; }
}

/* ------------------------------------------ sport colors (v1.69.0)
 *
 * 대표님: 「파크 보라점 야구는 파란점 축구는빨강점 풋살은 분홍점 ... 종목별 누르면 각각
 * 종목별 색깔이 나오면 회원들이 보기 편할듯합니다」 (2026-09-24).
 *
 * One palette (tokens.css --sc-*), four groups plus a fallback:
 *   parkgolf (PARKGOLF + SCREENPARK) purple, baseball (incl. T-ball, youth) blue,
 *   soccer red, futsal bright green (pink until v1.69.1, owner 2026-09-25: 「풋살도 초록색」),
 *   anything else keeps the darker club green (--sc-etc).
 * The older --sp-* tokens are left untouched (admin and legacy screens still read them);
 * this block only repaints the member-facing hooks by class, and it must stay near the
 * END of app.css so it wins over the v1.67 <=480 block (same specificity, later wins).
 */
.sport-parkgolf, .sport-screenpark, .sc-parkgolf { --sc: var(--sc-parkgolf); --sc-ink: var(--sc-parkgolf-ink); --sc-bg: var(--sc-parkgolf-bg); }
.sport-baseball, .sc-baseball { --sc: var(--sc-baseball); --sc-ink: var(--sc-baseball-ink); --sc-bg: var(--sc-baseball-bg); }
.sport-soccer, .sc-soccer { --sc: var(--sc-soccer); --sc-ink: var(--sc-soccer-ink); --sc-bg: var(--sc-soccer-bg); }
.sport-futsal, .sc-futsal { --sc: var(--sc-futsal); --sc-ink: var(--sc-futsal-ink); --sc-bg: var(--sc-futsal-bg); }
.sport-badminton, .sc-etc { --sc: var(--sc-etc); --sc-ink: var(--sc-etc-ink); --sc-bg: var(--sc-etc-bg); }

/* calendar: one dot per sport on a day, side by side */
.cal .d .dots { display: flex; gap: 3px; justify-content: center; flex-wrap: wrap; max-width: 100%; min-height: 8px; }
.cal .d .dot, .cal-legend .dot { display: block; width: 8px; height: 8px; border-radius: 50%; background: var(--sc, var(--sc-etc)); flex: 0 0 auto; }
.cal-legend {
  list-style: none; margin: 0 0 var(--s3); padding: 0; display: flex; flex-wrap: wrap; gap: 6px 14px;
  font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2);
}
.cal-legend li { display: inline-flex; align-items: center; gap: 6px; }
.cal-legend .dot { width: 10px; height: 10px; }
.cal .cal-ent[class*="sc-"] { border-left: 5px solid var(--sc); }

/* match cards, badges, avatars */
.match-card[class*="sport-"]::before { background: var(--sc, var(--ink-3)); }
.badge[class*="sport-"] { color: var(--sc-ink, var(--ink)); background: var(--sc-bg, var(--bg)); border-color: var(--sc, var(--line)); }
.list .li .avatar[class*="sport-"] { color: var(--sc-ink); background: var(--sc-bg); border-color: var(--sc); }

/* sport picker: stripe in the sport color, the chosen tile filled with it */
.sportpick .sp-tile[class*="sport-"] { border-left-color: var(--sc); }
.sportpick .sp-tile.on[class*="sport-"] { background: var(--sc-ink); }
/* 전체 종목 chosen: a band of all four colors, not one of them */
.sportpick .sp-all.on {
  background: var(--ink);
  background-image: linear-gradient(90deg, var(--sc-parkgolf) 0 25%, var(--sc-baseball) 25% 50%, var(--sc-soccer) 50% 75%, var(--sc-futsal) 75% 100%);
  background-size: 100% 6px; background-repeat: no-repeat; background-position: bottom;
}
/* sticky "지금 보는 종목" bar */
.sportnow.sn-sport { border-bottom: 3px solid var(--sc); }
.sportnow.sn-sport .sn-btn { background: var(--sc-bg); border-color: var(--sc); color: var(--sc-ink); }
.sportnow.sn-sport .go { color: var(--sc-ink); }
.sportnow.sn-all { border-bottom: 3px solid transparent; border-image: linear-gradient(90deg, var(--sc-parkgolf) 0 25%, var(--sc-baseball) 25% 50%, var(--sc-soccer) 50% 75%, var(--sc-futsal) 75% 100%) 1; }

/* ------------------------------------------ bottom tab bar, KakaoTalk style (v1.69.0)
 *
 * 대표님: 「앱화면도 카카오톡처럼 젤밑에 하단에 하나 만들어주면 좋겠습니다」 (2026-09-24).
 * The bar itself already existed (partials/tabbar.ejs, fixed, safe-area padded, .wrap
 * reserves --tab-h + safe area). What was missing: the icon pill + bold label only
 * applied at <=480, so 481-768 tablets and wide phones in landscape got the old flat
 * look. Now the same look runs up to 768. Above 768 it stays (it is the only nav in
 * the app) but shrinks to a centered, rounded dock so it does not read as a phone UI
 * stretched across a monitor. Pages without a tab bar (login, signup, intro home,
 * error) never include the partial, so nothing changes there.
 */
@media (max-width: 768px) {
  .tabbar {
    border-top: 0; box-shadow: 0 -1px 0 var(--line), 0 -6px 18px rgba(20, 24, 29, .08);
    background: rgba(255, 255, 255, .97);
  }
  .tabbar a { gap: 3px; font-size: var(--fs-sm); color: var(--ink-2); -webkit-tap-highlight-color: transparent; }
  .tabbar a .i { width: 56px; height: 30px; padding: 3px 16px; border-radius: 999px; box-sizing: border-box; transition: background-color .15s; }
  .tabbar a.on { box-shadow: none; color: var(--turf-dark); font-weight: 800; }
  .tabbar a.on .i { background: var(--turf-bg); }
  .tabbar a:active .i { background: var(--line-2); }
}
@media (min-width: 769px) {
  .tabbar {
    max-width: 600px; left: 0; right: 0; margin: 0 auto;
    border: 1px solid var(--line); border-bottom: 0; border-radius: 18px 18px 0 0;
    box-shadow: 0 -4px 16px rgba(20, 24, 29, .08);
  }
  .tabbar a { flex-direction: row; gap: 8px; }
  .tabbar a.on { box-shadow: inset 0 -3px 0 var(--turf-dark); }
  /* the dock is fixed, so the page must end above it */
  .wrap:has(~ .tabbar) { padding-bottom: calc(var(--tab-h) + 32px + env(safe-area-inset-bottom)); }
}

/* v1.69.0 launch splash. Only visible when head.ejs put .ls-on on <html>
   (home-screen app, cold start). Same #f2f3f5 as manifest background_color. */
.launch-splash { display: none; }
html.ls-on .launch-splash {
  display: flex; position: fixed; inset: 0; z-index: 2147483000;
  flex-direction: column; align-items: center; justify-content: center; gap: 18px;
  background: #f2f3f5; opacity: 1; transition: opacity .35s ease;
  padding: env(safe-area-inset-top) 24px env(safe-area-inset-bottom);
}
html.ls-on body { overflow: hidden; }
html.ls-out .launch-splash { opacity: 0; pointer-events: none; }
.launch-splash img { width: 112px; height: 112px; border-radius: 26px; object-fit: contain; }
.launch-splash b {
  font-size: 30px; font-weight: 900; letter-spacing: -0.02em; color: #1f242b;
  line-height: 1.2; text-align: center;
}
.launch-splash b span { display: inline-block; white-space: nowrap; }
@media (prefers-reduced-motion: reduce) { .launch-splash { transition: none; } }

/* ---------------------------------------------------------------------------
 * v1.69.0 후원사 크게 보기 (public/js/sponsorbox.js)
 * 대표님: 「후원사 누르면 크게 보여야되고 돌아가게끔 부탁」.
 * 어두운 바깥 + 가운데 흰 카드. 큰 로고, 이름, 주소가 있으면 [홈페이지 가기].
 * 닫기 단추 둘(X, 닫기)은 48px 이상이다. 움직임은 색과 투명도만.
 * ------------------------------------------------------------------------- */
.sbox {
  position: fixed; inset: 0; z-index: 2147482000;
  display: flex; align-items: center; justify-content: center;
  padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
  background: rgba(15, 18, 24, .72);
}
.sbox[hidden] { display: none; }
.sbox-card {
  position: relative; width: min(100%, 420px); max-height: 100%; overflow: auto;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 56px 20px 20px; border-radius: 20px; background: var(--card, #fff);
  box-shadow: 0 18px 48px rgba(0, 0, 0, .28); text-align: center;
}
.sbox-x {
  position: absolute; top: 6px; right: 6px; width: 48px; height: 48px;
  border: 0; border-radius: 50%; background: transparent; color: var(--ink, #1f242b);
  font-size: 30px; line-height: 1; cursor: pointer; font-family: inherit;
}
.sbox-x:active { background: var(--card-2, #f2f3f5); }
.sbox-logo {
  width: min(72vw, 260px); aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  border-radius: 18px; background: #fff; border: 1px solid var(--line, #e3e5e8); overflow: hidden;
}
.sbox-logo.wide { width: 100%; aspect-ratio: 2 / 1; }
.sbox-logo img { width: 100%; height: 100%; object-fit: contain; padding: 10px; }
.sbox-ini { font-size: 96px; font-weight: 900; color: var(--turf, #1f8a4c); }
.sbox-nm { font-size: 22px; font-weight: 900; line-height: 1.3; color: var(--ink, #1f242b); word-break: keep-all; }
.sbox-ln { margin: 0; font-size: 16px; color: var(--ink-2, #555); word-break: keep-all; }
.sbox-go, .sbox-close { width: 100%; min-height: 52px; font-size: 18px; }
.sbox-go[hidden] { display: none; }
html[data-lb-open] body { overflow: hidden; }
/* 주소 없는 칸(div)도 이제 누를 수 있다. */
.sponsors div.sponsor, .afflstrip div.sponsor, div.mainspon { cursor: pointer; }

/* ---------------------------------------------------------------------------
 * v1.69.0 notices as sponsor-sized tiles
 * 대표님: 「공지상황도 후원사처럼 박스크기 똑같이 부탁」 (2026-09-24).
 *
 * Each notice in a list is now the same box as one sponsor slot (measured, not guessed):
 *   width   = (row - 2 gaps - 1 gap of side padding) / 3   -> 112.66px at 390, 234.66px at 768
 *   height  = logo (--doc-pic, 72px below 768) + 71px      -> 143px at 390, 191px at 768, 271px at 1600
 *   radius  = 14px at <=480, --r above (the sponsor slot's values)
 *   gap     = 8px, 12px from 768 (the sponsor band's --slide-gap)
 * The tile shows the pin badge, the title (2 lines), the date (1 line) and the words
 * "전체 보기". A tap opens the whole notice (text, photos, files) in the same pop-up as
 * the sponsors (public/js/sponsorbox.js). The row still swipes sideways (v1.68.0).
 * Gated on html.js: with no JavaScript the pop-up cannot open, so the notices stay the
 * old full cards and nothing becomes unreadable. Same markup on home and /notices.
 * ------------------------------------------------------------------------- */
.notice .nt-go { display: none; }
html.js .notice-list {
  --nt-gap: 8px;
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - 2 * var(--nt-gap)) / 3);
  column-gap: var(--nt-gap);
  align-items: stretch;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scrollbar-width: none; -ms-overflow-style: none;
  padding: 2px calc(var(--nt-gap) / 2) 6px;
  scroll-padding-left: calc(var(--nt-gap) / 2);
}
html.js .notice-list::-webkit-scrollbar { display: none; }
html.js .notice-list:not(:has(> .notice:nth-child(2))) { grid-auto-columns: calc((100% - 2 * var(--nt-gap)) / 3); }
html.js .notice-list > .notice {
  scroll-snap-align: start; min-width: 0;
  height: calc(var(--doc-pic, 72px) + 71px);
  display: flex; flex-direction: column; gap: 6px;
  padding: 10px 8px; overflow: hidden; cursor: pointer;
  border-radius: var(--r);
}
html.js .notice-list > .notice .hd { display: block; }
html.js .notice-list > .notice .hd .badge { display: inline-block; margin-bottom: 4px; font-size: var(--fs-xs); }
html.js .notice-list > .notice .hd h3 {
  font-size: var(--fs-sm); line-height: 1.3; font-weight: 800; letter-spacing: -0.2px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  word-break: break-all;
}
html.js .notice-list > .notice .dt {
  display: block; margin: 0; padding: 0; background: none; border-radius: 0;
  font-size: var(--fs-xs); color: var(--ink-3); font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
html.js .notice-list > .notice .nt-go {
  display: block; margin-top: auto; font-size: var(--fs-xs); font-weight: 800;
  color: var(--turf-dark); text-decoration: underline; text-underline-offset: 3px;
}
html.js .notice-list > .notice > .fold,
html.js .notice-list > .notice > .notice-pics,
html.js .notice-list > .notice > .notice-more-pic,
html.js .notice-list > .notice > .notice-docs,
html.js .notice-list > .notice > .more { display: none; }
@media (min-width: 768px) {
  /* a mouse cannot swipe a row with no scrollbar: from 768 the same tiles wrap, 3 a row */
  html.js .notice-list {
    --nt-gap: 12px;
    grid-auto-flow: row; grid-template-columns: repeat(3, calc((100% - 2 * var(--nt-gap)) / 3));
    row-gap: var(--nt-gap); overflow: visible;
  }
  html.js .notice-list > .notice { padding: 14px 12px; gap: 8px; }
  html.js .notice-list > .notice .hd h3 { font-size: var(--fs-md); }
  html.js .notice-list > .notice .dt, html.js .notice-list > .notice .nt-go { font-size: var(--fs-sm); }
}
@media (max-width: 480px) {
  html.js .notice-list > .notice { border-radius: 14px; }
}
@media (hover: hover) and (pointer: fine) {
  html.js .notice-list > .notice:hover { border-color: var(--turf); }
}
html.js .notice-list > .notice:focus-visible { outline: 3px solid var(--turf); outline-offset: 2px; }

/* the whole notice inside the pop-up: a wider card, left-aligned reading text */
.sbox-card.note { width: min(100%, 640px); align-items: stretch; text-align: left; padding-top: 56px; }
.sbox-card.note .sbox-logo, .sbox-card.note .sbox-nm, .sbox-card.note .sbox-ln, .sbox-card.note .sbox-go { display: none; }
.sbox-body { display: none; }
.sbox-card.note .sbox-body { display: block; }
.sbox-body .notice { border: 0; padding: 0; box-shadow: none; background: none; }
.sbox-body .notice .hd h3 { font-size: 22px; line-height: 1.35; }
.sbox-body .notice .nt-go { display: none; }

/* ------------------------------------------ v1.70.0 docx items 07 and 09
 *
 * 07 「가맹점 화면에서 항목 사이를 구분할 수 있는 경계선을 개선 굵게」: the merchant
 * list (/merchants) and the merchant's own screen (/merchant) used the faint 1px
 * --line-2 row line. Make it a 2px --line row divider and a 2px outer border there only;
 * other lists keep the old look.
 *
 * 09 「클럽 소식의 사진을 한 개씩 크게 배치하는 대신, 한 줄에 2개 또는 3개씩」: on the
 * member home (matches.ejs, app.css only) `.rotbox.posters` fell back to the shared
 * `.rotbox` --per:1, one big poster per row on phones. Same numbers as home.css
 * (2 on phones, 3 from 768px, 4 from 1024px) so both screens match. Class count
 * stays (0,2,0) to beat `.rotbox.multi` in the media queries above.
 */
.list.mchlist { border-width: 2px; border-color: var(--line); }
.list.mchlist > .li { border-bottom: 2px solid var(--line); }
.list.mchlist > .li:last-child { border-bottom: 0; }
.rotbox.posters { --per: 2; --slide-gap: 12px; }
@media (min-width: 768px) { .rotbox.posters { --per: 3; --slide-gap: 14px; } }
@media (min-width: 1024px) { .rotbox.posters { --per: 4; --slide-gap: 16px; } }
.posters .poster img { margin-left: auto; margin-right: auto; }

/* v1.70.0 docx 01: sport tiles, scope tabs and the pager link to /?sport=X...#book so the
   reloaded page lands on the match list instead of the top. The anchor must clear the
   sticky app bar plus the sticky sport strip (.sportnow, visible once the grid is gone). */
#book { scroll-margin-top: calc(var(--bar-h) + env(safe-area-inset-top) + 76px); }
/* v1.74.0: 「지난 경기 한번에 정리」 확인 상자로 뛰어올 때 제목(「지난 경기 N개를 정리합니다」)이 위 막대에 가리지 않게. */
#archive-confirm { scroll-margin-top: calc(var(--bar-h) + env(safe-area-inset-top) + 16px); }

/* ------------------------------------------ one-tap join under a match card (v1.72.1)
 * 대표님 「종목별 바로찍어서 예약할수있는지」 (2026-09-29). The card stays one link to the
 * detail page; the join button sits right under it as a sibling (a form cannot live inside
 * an <a>). Joined to the card so it reads as one block, filled with the sport color. */
.mc-item { display: grid; }
.mc-item.has-act > .match-card { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom-width: 0; }
.mc-act {
  background: var(--card); border: 1px solid var(--line); border-top: 0;
  border-radius: 0 0 var(--r) var(--r); padding: 0 12px 12px 20px; position: relative; overflow: hidden;
}
@media (max-width: 480px) { .mc-act { border-color: var(--line-2); border-radius: 0 0 16px 16px; } }
.mc-act::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 6px; background: var(--sc, var(--ink-3)); }
.mc-act form { margin: 0; }
.mc-join {
  display: flex; align-items: center; justify-content: center; width: 100%; min-height: var(--tap);
  border: 0; border-radius: 12px; background: var(--sc-ink, var(--turf)); color: #fff;
  font: inherit; font-size: var(--fs-control); font-weight: 800; text-decoration: none; cursor: pointer;
}
.mc-join:active { filter: brightness(.88); }
.mc-join:disabled { background: var(--ink-2); }

/* ------------------------------------------ quick apply panel under the sport tiles (v1.73.0)
 * The customer (2026-09-30): 「여기서 종목별 눌러서 예약가능한가요」 / 「축구를 눌러서 바로는 안되나요」.
 * Sits right under the scope tabs (#book), so a sport tile tap lands on a 신청 button, no scroll.
 * Colours follow the sport tokens (--sc, --sc-ink, --sc-bg) set by .sport-<x>; no per-sport rule here.
 * Lives in app.css because matches.ejs does not load home.css. */
.qa {
  background: var(--card); border: 2px solid var(--sc, var(--line)); border-radius: var(--r);
  padding: 12px 12px 4px; margin: 0 0 14px; position: relative;
}
.qa-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.qa-t { font-size: var(--fs-md, 19px); font-weight: 900; color: var(--sc-ink, var(--ink)); }
.qa-n { font-size: var(--fs-sm); font-weight: 800; color: var(--sc-ink, var(--ink-2)); background: var(--sc-bg, var(--turf-bg)); border-radius: 999px; padding: 3px 10px; white-space: nowrap; }
.qa-none { padding: 14px 4px 16px; font-size: var(--fs-base); font-weight: 800; color: var(--ink-2); text-align: center; }
/* v1.75.0: the empty panel is no longer a dead end ("바로신청들어가면 신청이 안눌러짐").
   One pressable 경기 열기 (sport prefilled) for whoever may open, else one waiting line. */
.qa-none { display: flex; flex-direction: column; align-items: stretch; gap: 10px; }
.qa-none .qa-btn { gap: 6px; }
.qa-none .qa-btn .i { width: 22px; height: 22px; }
.qa-none-s { font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2); }
/* Keep the panel's buttons clear of the fixed tabbar when the page lands on #book. */
.qa, .qa-row, .qa-none { scroll-margin-bottom: calc(var(--tab-h) + 16px + env(safe-area-inset-bottom)); }
.qa-flash { margin-bottom: 10px; }
.qa-row {
  display: flex; align-items: center; gap: 10px; padding: 10px 0; border-top: 1px solid var(--line);
}
.qa-head + .qa-row, .qa-flash + .qa-row { border-top: 0; }
.qa-row.is-mine { background: var(--turf-bg); margin: 0 -12px; padding: 10px 12px; }
.qa-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; color: var(--ink); text-decoration: none; }
.qa-title { font-size: var(--fs-md, 19px); font-weight: 900; line-height: 1.3; }
.qa-when { font-size: var(--fs-base); font-weight: 800; line-height: 1.35; }
.qa-where { font-size: var(--fs-base); font-weight: 700; color: var(--ink-2); line-height: 1.35; overflow-wrap: anywhere; }
/* v1.76.1: 구장이 맨 위(경기 이름 자리), 그 아래 개설자. */
.qa-row .qa-where, .qa-item .qa-where { font-size: var(--fs-md, 19px); font-weight: 900; color: var(--ink); line-height: 1.3; }
.qa-host { font-size: var(--fs-base); font-weight: 700; color: var(--ink-2); line-height: 1.35; overflow-wrap: anywhere; }
.qa-sub { font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2); }
.qa-do { flex: none; display: flex; flex-direction: column; align-items: stretch; gap: 6px; min-width: 96px; }
.qa-do form { margin: 0; }
.qa-btn {
  display: flex; align-items: center; justify-content: center; min-height: var(--tap); width: 100%;
  padding: 0 14px; border: 0; border-radius: 12px; background: var(--sc-ink, var(--turf)); color: #fff;
  font: inherit; font-size: var(--fs-control); font-weight: 900; text-decoration: none; cursor: pointer; white-space: nowrap;
}
.qa-btn:active { filter: brightness(.88); }
.qa-btn:disabled { background: var(--ink-2); }
.qa-btn.qa-pay { background: var(--warn); }
.qa-done {
  display: flex; align-items: center; justify-content: center; gap: 4px; min-height: 44px; padding: 0 10px;
  border-radius: 12px; background: var(--turf); color: #fff; font-size: var(--fs-base); font-weight: 900; white-space: nowrap;
}
.qa-done .i { width: 20px; height: 20px; }
.qa-cell { font-size: var(--fs-sm); font-weight: 800; color: var(--turf-dark, var(--ink)); text-align: center; }
.qa-cancel button {
  width: 100%; min-height: 40px; border: 1.5px solid var(--line); border-radius: 10px; background: var(--card);
  color: var(--ink-2); font: inherit; font-size: var(--fs-sm); font-weight: 800; cursor: pointer;
}
.qa-rest { font-size: var(--fs-sm); font-weight: 800; color: var(--ink-2); text-align: center; }
/* One open match: a confirm card. Details stacked, one full-width button under them. */
.qa.qa-one .qa-row { flex-direction: column; align-items: stretch; gap: 12px; padding-bottom: 12px; }
.qa.qa-one .qa-row.is-mine { margin: 0 -12px -4px; border-radius: 0 0 var(--r) var(--r); }
.qa.qa-one .qa-do { min-width: 0; }
.qa.qa-one .qa-btn { min-height: 60px; font-size: var(--fs-lg, 21px); }
.qa.qa-one .qa-done { min-height: 56px; font-size: var(--fs-md, 19px); }

/* v1.77.0 (2026-10-01) 「예약하기 ... 종목별로 구장이나왔으면」: the home screen's sport tiles, placed
   inside the page column on /reservations. The home band is full-bleed; here it is a card, so the
   side padding trick from the home screen is switched off. */
.sportpick.in-wrap { border: 1.5px solid var(--line); border-radius: var(--r); margin: 0 0 14px; padding: 10px; }
@media (min-width: 768px) {
  .sportpick.in-wrap { padding-left: 10px; padding-right: 10px; }
  .sportpick.in-wrap .sp-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
}
.sportpick.in-wrap .sp-tile { min-height: 0; }
.venue-pick-h { margin: 0 0 8px; font-size: var(--fs-base); color: var(--ink-2); }
.venue-pick-h b { color: var(--ink); }
/* Slot avatar now reads 「오전 / 9」 on two lines (was 24h 「09」). */
.slotlist .li.slot .avatar { display: flex; flex-direction: column; justify-content: center; line-height: 1.05; width: 52px; }
.slotlist .li.slot .avatar .ap { font-size: 13px; font-weight: 800; }
/* 「구장이름 보단. 개설자」: the opener of a match stands out on its slot row and match cards. */
.slot-host { font-size: var(--fs-md); color: var(--ink); }
.slot-host b { font-weight: 900; }
.slot-match { color: var(--ink-2); font-size: var(--fs-sm); margin-left: 4px; }
.qa-host { font-size: var(--fs-md, 19px); font-weight: 800; color: var(--ink); }

/* v1.77.0 venue picker (partials/venue-picker): a tile per registered venue, name on top and the
 * sports it serves underneath. Only the picked sport's venues are shown. */
.venue-pick .pick span { flex-direction: column; gap: 2px; min-height: 72px; }
.venue-pick .vp-name { font-weight: 900; font-size: var(--fs-control); line-height: 1.25; word-break: keep-all; }
.venue-none {
  margin: 10px 0 0; padding: 14px 16px; border-radius: var(--r); border: 2px dashed var(--line);
  background: var(--card); font-size: var(--fs-base); font-weight: 700; color: var(--ink-2);
}
.venue-legacy { margin: 0 0 10px; font-size: var(--fs-base); color: var(--ink); }
/* 오전/오후 time select (partials/time-select): large enough to read at 175% zoom. */
select.time-ko { font-weight: 800; }
@media (max-width: 380px) { .venue-pick { grid-template-columns: 1fr; } }

/* v1.77.1 정식 홈페이지 바로가기 as a row of tiles (partials/official-site, 2+ links).
 * 대표님: 「이부분도 가로로 제목만나오게 부탁」 (2026-10-01). Same look as the notice tiles
 * (v1.69.0): three equal boxes, white card, title only, 2-line clamp, turf border on hover.
 * Three always fit in one row, so there is no sideways scroll here. Grid stretch keeps the
 * tiles the same height when one title wraps. */
.offsite .off-row {
  --off-gap: 8px;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--off-gap);
  align-items: stretch;
}
.offsite .off-row .li {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  min-width: 0; min-height: 76px; padding: 10px 8px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  color: var(--ink); text-decoration: none; text-align: center;
}
.offsite .off-row .off-ic { display: inline-flex; color: var(--ink-2); }
.offsite .off-row .off-ic .i { width: 22px; height: 22px; }
.offsite .off-row .li .t {
  font-size: var(--fs-sm); line-height: 1.3; font-weight: 800; letter-spacing: -0.2px; color: var(--ink);
  word-break: keep-all; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.offsite .off-row .li:focus-visible { outline: 3px solid var(--turf); outline-offset: 2px; }
.offsite.hero .off-row .li:focus-visible { outline-color: #fff; }
@media (hover: hover) and (pointer: fine) {
  .offsite .off-row .li:hover { border-color: var(--turf); }
}
@media (min-width: 768px) {
  .offsite .off-row { --off-gap: 12px; }
  .offsite .off-row .li { min-height: 88px; padding: 14px 12px; gap: 8px; }
  .offsite .off-row .li .t { font-size: var(--fs-md); }
}
@media (min-width: 1024px) {
  .offsite.hero .off-row { max-width: 960px; margin-left: 0; margin-right: auto; }
}
@media (max-width: 480px) {
  .offsite .off-row .li { border-radius: 14px; }
}
/* 「선수등록하는곳」 needs 90px on one line; a 360 tile with 8px sides leaves 89. */
@media (max-width: 400px) {
  .offsite .off-row .li { padding-left: 6px; padding-right: 6px; }
}

/* v1.79.0 대진표 (views/partials/bracket.ejs). 판을 위에서 아래로 쌓는다. 390 폭에서도 글씨 17px. */
.bk-round { display: grid; gap: 10px; }
@media (min-width: 900px) { .bk-roundcard .bk-round { grid-template-columns: 1fr 1fr; } }
.bk-match { border: 1px solid var(--line); border-radius: 12px; background: var(--card); overflow: hidden; scroll-margin-top: 80px; }
.bk-match.done { border-color: var(--turf-line); }
.bk-side { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 8px 12px; font-size: var(--fs-base); border-left: 5px solid transparent; }
.bk-side + .bk-side { border-top: 1px solid var(--line); }
.bk-side .nm { flex: 1 1 auto; word-break: keep-all; }
.bk-side .sc { flex: 0 0 auto; min-width: 2.2em; text-align: right; font-weight: 800; font-size: var(--fs-md); }
.bk-side.won { font-weight: 800; border-left-color: var(--turf); background: var(--turf-bg); color: var(--turf-dark); }
.bk-side.empty .nm { color: var(--ink-3); }
.bk-note { padding: 6px 12px; font-size: var(--fs-sm); color: var(--ink-2); }
.bk-form { padding: 10px 12px 12px; border-top: 1px dashed var(--line); display: grid; gap: 10px; }
.bk-scores { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.bk-scores label { display: grid; gap: 4px; font-size: var(--fs-sm); font-weight: 700; }
.bk-scores input { min-height: 52px; font-size: 20px; text-align: center; }
.bk-champ { margin: 0 0 12px; padding: 14px 16px; border-radius: 14px; background: var(--turf); color: #fff; font-size: var(--fs-md); text-align: center; }
.bk-table-wrap { overflow-x: auto; margin-bottom: 12px; }
.bk-table { width: 100%; border-collapse: collapse; font-size: var(--fs-base); }
.bk-table th, .bk-table td { padding: 8px 6px; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
.bk-table th { font-size: var(--fs-sm); color: var(--ink-2); font-weight: 700; }
.bk-table .l { text-align: left; white-space: normal; word-break: keep-all; }

/* ------------------------------------------------- 관련사이트 banner (v1.82.0)
 *
 * 대표님's own picture (views/partials/site-foot.ejs). The tap zones are absolutely positioned in
 * percent of the image box, so they track the picture at any width. At 390px the image is 358px
 * wide and each zone is about 50px tall (>= --tap 48px). Max 480px so the picture never becomes
 * a wall on desktop. Focus ring is drawn on the zone itself (yellow, readable on the light buttons).
 */
.sitefoot .relbanner .rb-img { position: relative; max-width: 480px; border-radius: 16px; overflow: hidden; line-height: 0; }
.sitefoot .relbanner .rb-img img { display: block; width: 100%; height: auto; }
.sitefoot .relbanner .rb-zone { position: absolute; display: block; border-radius: 14px; min-height: 48px; }
.sitefoot .relbanner a.rb-zone:hover { background: rgba(255, 255, 255, .12); }
.sitefoot .relbanner a.rb-zone:focus-visible { outline: 4px solid #ffd23f; outline-offset: -4px; box-shadow: 0 0 0 2px #14181d inset; }
.sitefoot .relbanner .rb-zone.off { background: rgba(20, 24, 29, .45); cursor: default; }
.sitefoot .relbanner .rb-soon {
  position: absolute; left: 8px; top: 6px; line-height: 1.2;
  padding: 3px 8px; border-radius: 8px; background: #14181d; color: #fff; font-size: 12px; font-weight: 800;
}
.sitefoot .relbanner .rb-note { margin: var(--s2) 0 0; font-size: var(--fs-sm); color: var(--bar-ink); }
.sitefoot .relbanner ul.rb-more { display: flex; flex-wrap: wrap; gap: 0 var(--s4); margin-top: var(--s2); }
.sitefoot .relbanner ul.rb-more a { min-height: var(--tap); display: inline-flex; align-items: center; padding: 0; }
.sitefoot .relbanner ul.rb-more a .t { font-size: var(--fs-sm); font-weight: 700; }
/* Debug: the shoot script adds class "rbdebug" to <html> to outline every zone (alignment evidence). */
.rbdebug .sitefoot .relbanner .rb-zone { outline: 3px dashed #ff00aa; outline-offset: -3px; background: rgba(255, 0, 170, .12); }

/* 내 종목만 보기 줄 (v1.82.0, views/partials/sport-view-toggle.ejs). */
.sportview {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s2); flex-wrap: wrap;
  margin: var(--s2) var(--wrap-pad) 0; padding: 6px 6px 6px 14px;
  background: var(--turf-bg); border: 1px solid var(--turf-line); border-radius: 12px;
  font-size: var(--fs-sm); color: var(--turf-dark); font-weight: 700;
}
.sportview .sv-t { word-break: keep-all; }
.sportview .sv-btn {
  display: inline-flex; align-items: center; min-height: var(--tap); padding: 0 16px;
  border-radius: 10px; background: #fff; border: 1px solid var(--turf-line);
  color: var(--turf-dark); font-weight: 800; text-decoration: none; white-space: nowrap;
}

/* v1.82.0 사용 설명서 button + 연습해보기 card on the admin home. */
.btn.admin-manual { margin: 0 0 var(--s3); min-height: 60px; padding: 8px var(--s4); line-height: 1.3; }
.btn.admin-manual .i { width: 24px; height: 24px; flex: none; }
.practice-card { margin-top: var(--s3); border-color: #e0a800; background: #fffaf0; }
.practice-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.practice-row form { margin: 0; }
.practice-row .btn { min-height: 52px; }
@media (max-width: 380px) { .practice-row { grid-template-columns: 1fr; } }
.practice-bar { margin: var(--s3) 0; padding: 12px 14px; border-radius: var(--r-sm); background: #fff3cd; border: 2px solid #e0a800; color: #6b4e00; font-weight: 800; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: space-between; }
.practice-bar form { margin: 0; }

/* v1.82.2 footer: 관련사이트 banner moved ABOVE the 사업자 정보 block (대표님 「밑에랑 자리이동」).
 * The banner block now carries the bottom divider and the business block starts after it.
 * 네이버 밴드 tile row under the banner, same rounded width as the picture. */
.sitefoot .relbanner { margin-top: 0; padding-top: 0; border-top: 0; margin-bottom: var(--s5); padding-bottom: var(--s4); border-bottom: 1px solid var(--bar-line); }
.sitefoot .relbanner .rb-tiles { max-width: 480px; margin-top: var(--s2); display: grid; gap: var(--s2); line-height: normal; }
.sitefoot .relbanner a.rb-tile { display: flex; align-items: center; gap: 12px; min-height: 56px; padding: 8px 14px; border-radius: 14px; background: #fff; color: #14181d; text-decoration: none; font-weight: 800; box-shadow: 0 2px 0 rgba(0, 0, 0, .18); }
.sitefoot .relbanner a.rb-tile .t { color: #14181d; font-size: 16px; }
.sitefoot .relbanner a.rb-tile .ic { line-height: 0; }
.sitefoot .relbanner a.rb-tile:focus-visible { outline: 4px solid #ffd23f; outline-offset: 2px; }

/* v1.82.5 대회 카드 (partials/tour-card.ejs): /tournaments list + the 「대회」 block above 공지사항.
 * One `.tcard` = the old /tournaments row (a.li, whole row is the tap target) + the 참가명단
 * details under it. Inside `.list` the card carries the divider, so the row itself has none. */
.list.tcards .tcard { border-bottom: 1px solid var(--line-2); }
.list.tcards .tcard:last-child { border-bottom: 0; }
.list.tcards .tcard > .li { border-bottom: 0; }
.tc-roster { margin: 0 16px 12px; border: 1px solid var(--line); border-radius: 12px; background: var(--card-2); }
.tc-roster > summary {
  display: flex; align-items: center; gap: 8px; min-height: var(--tap); padding: 0 14px;
  cursor: pointer; list-style: none; font-weight: 800; font-size: var(--fs-base);
}
.tc-roster > summary::-webkit-details-marker { display: none; }
.tc-roster > summary::after { content: "▾"; font-size: 15px; line-height: 1; }
.tc-roster[open] > summary::after { content: "▴"; }
.tc-roster .tc-k { flex: 1; min-width: 0; }
.tc-roster .tc-go { font-size: var(--fs-sm); color: var(--turf); white-space: nowrap; }
.tc-roster .tc-opened { display: none; }
.tc-roster[open] .tc-opened { display: inline; }
.tc-roster[open] .tc-closed { display: none; }
.tc-roster > summary:focus-visible { outline: 3px solid var(--turf); outline-offset: 2px; border-radius: 12px; }
.tc-names { list-style: none; margin: 0; padding: 0 14px 8px; }
.tc-names li { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; min-height: 44px; padding: 6px 0; border-top: 1px solid var(--line-2); font-size: var(--fs-base); }
.tc-names .tc-no { min-width: 26px; color: var(--ink-2); font-weight: 700; }
.tc-names .tc-name { font-weight: 700; word-break: keep-all; overflow-wrap: anywhere; }
.tc-none { margin: -6px 16px 12px 69px; font-size: var(--fs-sm); color: var(--ink-2); }
.home-tours { margin-top: var(--s5); }
.home-tours-all {
  display: flex; align-items: center; justify-content: center; min-height: var(--tap);
  margin-top: var(--s2); font-weight: 800; color: var(--turf-dark); text-decoration: underline;
}
/* v1.86.0 footer league row (lib/footlinks.ts FOOT_LEAGUE_DEFS). Inline, wraps at 390. */
.sitefoot .foot-leagues { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin: var(--s3) 0 0; padding: var(--s3) 0; border-top: 1px solid rgba(255,255,255,.12); font-size: 15px; line-height: 1.4; }
.sitefoot .foot-leagues b { color: #9aa3ad; font-size: 13px; font-weight: 700; margin-right: 2px; }
.sitefoot .foot-leagues a { color: #fff; font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }
.sitefoot .foot-leagues .off { color: #7d8691; font-weight: 600; }
.sitefoot .foot-leagues .off em { font-style: normal; font-size: 11px; padding: 1px 6px; margin-left: 2px; border-radius: 10px; background: rgba(255,255,255,.1); color: #aab2bb; vertical-align: 1px; }
.sitefoot .legal .foot-leagues { width: 100%; margin: 0 0 var(--s2); padding: 0 0 var(--s2); border-top: 0; border-bottom: 1px solid rgba(255,255,255,.12); }

/* v1.87.0 종목별 부관리자: sport checkboxes on the 부관리자 screen (big tap targets). */
.op-sports { margin-top: 10px; }
.op-sports .chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0; }
.op-sports .chip { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 6px 12px; border: 1px solid var(--line, #d6dbe1); border-radius: 22px; font-size: 16px; background: #fff; }
.op-sports .chip input { width: 20px; height: 20px; margin: 0; }
.sc-home .sc-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.sc-home .sc-tiles a { display: block; padding: 16px; border: 1px solid var(--line, #d6dbe1); border-radius: 12px; font-weight: 700; font-size: 17px; background: #fff; text-decoration: none; color: inherit; }

/* v1.87.0 종목별 영상 */
.sv-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 14px; }
.sv-tabs a { padding: 8px 12px; border: 1px solid var(--line, #d6dbe1); border-radius: 18px; text-decoration: none; color: inherit; font-size: 15px; }
.sv-tabs a.on { background: var(--turf, #1f8a4c); color: #fff; border-color: transparent; }
.sv-form label { display: block; margin: 10px 0; } .sv-form label span { display: block; font-size: 14px; margin-bottom: 4px; }
.sv-form input, .sv-form select { width: 100%; min-height: 44px; font-size: 16px; }
.sv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
.sv { margin: 0; } .sv video, .sv img { width: 100%; border-radius: 10px; display: block; background: #000; aspect-ratio: 16/9; object-fit: cover; }
.sv-yt summary { list-style: none; position: relative; cursor: pointer; } .sv-yt summary::-webkit-details-marker { display: none; }
.sv-yt[open] summary { display: none; }
.sv-play { position: absolute; inset: 0; display: grid; place-items: center; font-size: 40px; color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,.6); }
.sv .yt { position: relative; aspect-ratio: 16/9; } .sv .yt iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; border-radius: 10px; }
.sv figcaption { font-size: 15px; margin-top: 6px; }
