/* 새 셸 화면별 완성도 층 — home(#/home 허브 홈: 히어로 · 이어서 작업 · 라이브러리 · 트렌드 요약). DESIGN.md(Framer) 준수 · body.shell-v2 스코프.
 *   파랑은 포커스·선택에만 · 칩은 중립(surface-2 + ink-80) · 카드는 표면 단.
 *   프로젝트 목록(#/projects)은 projects-list.css — 두 파일은 선택자가 겹치지 않아 순서와 무관하지만 index.html 에서 이 파일 바로 뒤에 둔다. */

/* ═══ 허브 홈 ═══ */
body.shell-v2 .hub-wrap { max-width: 1200px; padding: var(--sp-xl) var(--page-gutter) var(--sp-section); gap: var(--sp-xxl); }


/* 스포트라이트(페이지당 하나) — 공통 배경 모션 위에 얇은 테두리만 가진 투명 배너(오너 지시 2026-09-15 — 카드 테두리 금지의 의도적 예외) */
body.shell-v2 .hub-head { padding: 36px 40px; min-height: 168px; align-items: flex-end; gap: var(--sp-lg);
  background: rgba(9, 9, 9, 0.18); border: 1px solid rgba(255, 255, 255, 0.16); }
body.shell-v2 .hub-head:hover { background: rgba(9, 9, 9, 0.18); border-color: rgba(255, 255, 255, 0.16); }
body.shell-v2 .hub-head .hub-title { font-size: 40px; line-height: 1.1; letter-spacing: -1.6px; }
/* 한글은 어절 단위로만 줄을 바꾼다(이름·단어가 중간에서 끊기지 않게) — framer.css 의 22ch 상한은 인사말이 길어져 풀고 남는 폭을 쓴다.
   비어 있는 동안(인사말 고르는 중)에도 한 줄 높이를 잡아 머리가 출렁이지 않게 한다. */
body.shell-v2 .hub-head .hub-title { flex: 1 1 320px; min-width: 0; max-width: none; min-height: 1lh; word-break: keep-all; overflow-wrap: anywhere; }
body.shell-v2 .hub-head .btn { min-height: 44px; flex: none; }

/* 섹션 머리 — 제목·보조(좌) + 전체 보기 링크(우) */
body.shell-v2 .hub-section { gap: var(--sp-md); }
body.shell-v2 .hub-section-head { align-items: center; justify-content: space-between; gap: 12px; }
body.shell-v2 .hub-section-heading { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; min-width: 0; }
body.shell-v2 .hub-section-title { margin: 0; }
body.shell-v2 .hub-section-sub { font-size: var(--fs-fine); font-variant-numeric: tabular-nums; }
/* 섹션 머리 전진 링크 — '전체 보기'(.hub-more). 모바일은 44px 히트 영역 */
body.shell-v2 .btn.hub-more {
  flex: none; min-height: 32px; margin-right: -12px; padding: 0 12px; border: 0; border-radius: var(--r-pill);
  background: transparent; color: var(--ink-80); font-size: 14px; font-weight: 500;
}
body.shell-v2 .btn.hub-more:hover { background: var(--surface-1); color: var(--ink); }
body.shell-v2 .btn.hub-more:focus-visible { outline: none; box-shadow: var(--ring-focus); }

/* 카드 공통 — 포커스는 안쪽 링(가로 스크롤 줄에서도 잘리지 않게) */
body.shell-v2 .hub-card { padding: 16px; gap: 10px; cursor: pointer; transition: background 0.15s ease; }
body.shell-v2 .hub-card:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }

/* 이어서 작업 — 썸네일 없는 글 카드: 모드 칩·단계 / 제목(2줄) / 진행 막대 + n/7 · 시각 */
body.shell-v2 .hub-cards--plans { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; }
body.shell-v2 .hub-card--plan { min-height: 140px; justify-content: space-between; }
body.shell-v2 .hub-card-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* 모드 칩 — 카드(#141414) 위 surface-2(#1c1c1c)는 거의 안 보였다(오너 피드백 2026-09-17). 흰 알파 채움 + 흰 글자로 올린다(색은 쓰지 않는다 — 파랑은 포커스·선택 전용). */
body.shell-v2 .hub-chip { flex: none; padding: 0 9px; border-radius: var(--r-pill); background: rgba(255, 255, 255, 0.14); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.10); color: var(--ink); font-size: var(--fs-fine); font-weight: 600; line-height: 22px; letter-spacing: var(--ls-fine); }
body.shell-v2 .hub-card--plan:hover .hub-chip { background: rgba(255, 255, 255, 0.18); }
body.shell-v2 .hub-card--plan .hub-card-stage { color: var(--ink-80); }
body.shell-v2 .hub-card-stage { min-width: 0; font-size: var(--fs-fine); color: var(--ink-48); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.shell-v2 .hub-card--plan .hub-card-title {
  font-size: var(--fs-title-3); font-weight: 500; line-height: 1.3; letter-spacing: -0.3px; color: var(--ink);
  white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
body.shell-v2 .hub-card-foot { display: flex; align-items: center; gap: 10px; }
body.shell-v2 .hub-progress { position: relative; flex: 0 1 88px; height: 3px; border-radius: var(--r-pill); background: var(--surface-3); overflow: hidden; }
body.shell-v2 .hub-progress::after { content: ''; position: absolute; inset: 0 auto 0 0; width: var(--p, 0%); border-radius: inherit; background: var(--ink-80); }
body.shell-v2 .hub-card-foot .hub-card-meta { font-size: 12px; white-space: nowrap; font-variant-numeric: tabular-nums; }

/* 뼈대(renderCarousel) — 실제 .hub-card--plan 과 같은 격자·패딩·테두리(오너 피드백 2026-09-17 "스켈레톤이 콘텐츠 모양과 달라"). */
body.shell-v2 .skel-hub-plans { --skel-min: 240px; --skel-gap: 12px; --skel-card-pad: 16px; --skel-card-bg: var(--parchment); --skel-card-r: var(--r-md); --skel-line-h: 12px; }
body.shell-v2 .skel-hub-plans .skel-card { border: 1px solid var(--hairline); min-height: 140px; justify-content: space-between; }

/* 라이브러리 미리보기 — 16:9 타일(캐릭터·배경 시트가 16:9 다), 이미지 없으면 이름 첫 글자 */
body.shell-v2 .hub-cards--gallery { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; } /* 8장 = 4×2 — 남는 칸 없이 */
body.shell-v2 .hub-card--asset { padding: 8px 8px 12px; gap: 4px; }
body.shell-v2 .hub-card--asset .hub-card-thumb { height: auto; aspect-ratio: 16 / 9; margin-bottom: 6px; overflow: hidden; border-radius: var(--r-md); }
/* 캐릭터 시트는 여러 컷이 가로로 붙은 넓은 이미지라 정사각 cover 가 가운데만 잘라 보였다(오너 피드백 2026-09-17) — 잘리지 않게 전체를 담는다. */
body.shell-v2 .hub-card--asset .hub-card-thumb img { border-radius: 0; object-fit: contain; transition: transform 0.3s ease; }
@media (hover: hover) and (prefers-reduced-motion: no-preference) { body.shell-v2 .hub-card--asset:hover .hub-card-thumb img { transform: scale(1.03); } }
body.shell-v2 .hub-card--asset .hub-card-title { padding: 0 4px; font-size: var(--fs-fine); font-weight: 500; }
body.shell-v2 .hub-card--asset .hub-card-meta { padding: 0 4px; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.shell-v2 .hub-card-thumb--empty { display: flex; align-items: center; justify-content: center; background: var(--surface-2); }
body.shell-v2 .hub-monogram { font-size: 28px; font-weight: 500; letter-spacing: -0.8px; color: var(--ink-48); }

/* 뼈대(renderGallery) — 실제 .hub-card--asset 과 같은 4열 고정 격자·16:9 썸네일. */
body.shell-v2 .skel-hub-gallery { --skel-cols: 4; --skel-gap: 12px; --skel-aspect: 16 / 9; --skel-thumb-r: var(--r-md); --skel-card-pad: 8px 8px 12px; --skel-line-h: 10px; }
body.shell-v2 .skel-hub-gallery .skel-card { border: 1px solid var(--hairline); }

/* 빈·오류 상태 — 문장 + 다음 행동 */
body.shell-v2 .hub-state { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: var(--sp-lg); border-radius: var(--r-xl); background: var(--surface-1); }
body.shell-v2 .hub-state .hub-muted { margin: 0; font-size: var(--fs-caption); color: var(--ink-80); }
body.shell-v2 .hub-state .btn--secondary { background: var(--surface-2); }
body.shell-v2 .hub-state .btn--secondary:hover { background: var(--surface-3); }

@media (max-width: 810px) { body.shell-v2 .btn.hub-more { min-height: 44px; margin-right: -10px; padding: 0 10px; } }

@media (max-width: 810px) {
  body.shell-v2 .hub-wrap { padding: var(--sp-lg) var(--sp-md) 64px; gap: 32px; }
  body.shell-v2 .hub-head { flex-direction: column; align-items: flex-start; min-height: 0; padding: 24px 20px; border-radius: var(--r-xl); gap: var(--sp-lg); }
  body.shell-v2 .hub-head .hub-title { font-size: 26px; line-height: 1.18; letter-spacing: -0.9px; }
  body.shell-v2 .hub-section-title { font-size: var(--fs-title-2); }
  /* 카드 줄은 가로로 넘기는 캐러셀 — 페이지는 가로로 넘치지 않는다(스크롤은 줄 안에서만) */
  body.shell-v2 .hub-cards--plans, body.shell-v2 .hub-cards--gallery {
    grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 76%;
    overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scroll-padding-inline: var(--sp-md);
    margin-inline: calc(-1 * var(--sp-md)); padding: 0 var(--sp-md) 4px; scrollbar-width: none;
  }
  body.shell-v2 .hub-cards--gallery { grid-auto-columns: 62%; }
  body.shell-v2 .hub-cards--plans::-webkit-scrollbar, body.shell-v2 .hub-cards--gallery::-webkit-scrollbar { display: none; }
  body.shell-v2 .hub-cards--plans > *, body.shell-v2 .hub-cards--gallery > * { scroll-snap-align: start; }
}

/* ═══ 트렌드 네 줄(homeTrends · trendRow · trendTiles — 2026-09-17) ═══
 *   줄마다 머리(제목·보조 / 조작·좌우 넘김·전체 보기) + 가로 트랙. 스크롤바는 숨기되 넘김 버튼·키보드·트랙패드로 넘긴다. */
body.shell-v2 .hub-trends { gap: var(--sp-xl); }
body.shell-v2 .trow { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
body.shell-v2 .trow-head { display: flex; align-items: center; justify-content: space-between; gap: 8px 12px; flex-wrap: wrap; }
body.shell-v2 .trow-heading { display: flex; align-items: baseline; gap: 10px; min-width: 0; flex-wrap: wrap; }
body.shell-v2 .trow-title { margin: 0; font-size: var(--fs-title-3); line-height: var(--lh-title-3); font-weight: 600; letter-spacing: -0.3px; color: var(--ink); }
body.shell-v2 .trow-sub { font-size: var(--fs-fine); color: var(--ink-48); font-variant-numeric: tabular-nums; }
body.shell-v2 .trow-tools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
body.shell-v2 .trow-ctl { display: flex; }
body.shell-v2 .trow-seg { display: inline-flex; gap: 2px; padding: 2px; border-radius: var(--r-pill); background: var(--surface-1); margin-right: 4px; }
body.shell-v2 .trow-seg-btn { border: 0; background: transparent; color: var(--ink-80); font: inherit; font-size: 13px; font-weight: 500; line-height: 28px; padding: 0 12px; border-radius: var(--r-pill); cursor: pointer; white-space: nowrap; }
body.shell-v2 .trow-seg-btn:hover { color: var(--ink); }
body.shell-v2 .trow-seg-btn.on { background: #ffffff; color: #000000; }
body.shell-v2 .trow-seg-btn:focus-visible, body.shell-v2 .trow-arrow:focus-visible, body.shell-v2 .trow-track:focus-visible { outline: none; box-shadow: var(--ring-focus); }
body.shell-v2 .trow-arrow { width: 32px; height: 32px; padding: 0; display: grid; place-items: center; border: 0; /* padding 0 — 전역 button 패딩(8px 15px)이 32px 안 내용 폭을 2px 로 눌러 아이콘이 오른쪽으로 밀렸다 */ border-radius: var(--r-pill); background: var(--surface-1); color: var(--ink); cursor: pointer; }
body.shell-v2 .trow-arrow:hover:not(:disabled) { background: var(--surface-2); }
body.shell-v2 .trow-arrow:disabled { color: var(--ink-48); opacity: 0.5; cursor: default; }
body.shell-v2 .trow--static .trow-arrow { display: none; }
body.shell-v2 .btn.trow-more, body.shell-v2 .btn.trow-reroll { min-height: 32px; padding: 0 10px; border: 0; border-radius: var(--r-pill); background: transparent; color: var(--ink-80); font-size: 14px; font-weight: 500; }
body.shell-v2 .btn.trow-more:hover, body.shell-v2 .btn.trow-reroll:hover { background: var(--surface-1); color: var(--ink); }
body.shell-v2 .trow-track {
  display: grid; grid-auto-flow: column; grid-auto-columns: 168px; gap: 12px;
  overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x proximity; scrollbar-width: none; border-radius: var(--r-md); padding-bottom: 2px;
}
body.shell-v2 .trow-track::-webkit-scrollbar { display: none; }
body.shell-v2 .trow-track > * { scroll-snap-align: start; }
body.shell-v2 .trow-track:has(> .ttile--article) { grid-auto-columns: 300px; }
body.shell-v2 .trow-track:has(> .ttile--rank) { grid-auto-columns: 320px; }
/* 뼈대(fillTileSkeleton) — 실제 타일과 같은 바깥 클래스라 열 폭·카드 배경은 그대로 받고, 안쪽 뼈만 크기를 맞춘다 */
body.shell-v2 .ttile--skel { cursor: default; pointer-events: none; }
body.shell-v2 .ttile--skel.ttile--article:hover, body.shell-v2 .ttile--skel.ttile--rank:hover { background: var(--surface-1); }
body.shell-v2 .ttile-skel-img { aspect-ratio: 3 / 4; border-radius: var(--r-md); }
body.shell-v2 .ttile-skel-title { margin-top: 10px; height: 12px; width: 88%; }
body.shell-v2 .ttile-skel-title2 { margin-top: 6px; height: 12px; width: 56%; }
body.shell-v2 .ttile-skel-sub { margin-top: 8px; height: 10px; }
body.shell-v2 .ttile--skel.ttile--article .skel-line { height: 10px; }
body.shell-v2 .ttile--skel.ttile--article .ttile-skel-head { height: 15px; }
body.shell-v2 .ttile-skel-tag { width: 56px; height: 20px; border-radius: var(--r-pill); }
body.shell-v2 .ttile-skel-num { width: 16px; height: 16px; justify-self: end; border-radius: var(--r-xs, 5px); }
body.shell-v2 .ttile-skel-body { gap: 6px; }
body.shell-v2 .ttile-skel-body .skel-line { height: 11px; }
body.shell-v2 .ttile-skel-body .skel-line--short { height: 9px; width: 40%; }
body.shell-v2 .ttile-skel-val { width: 32px; height: 11px; }
body.shell-v2 .ttile--skel .ttile-rankrow { min-height: 33px; }

body.shell-v2 .ttile { display: flex; flex-direction: column; min-width: 0; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left; text-decoration: none; cursor: pointer; }
body.shell-v2 .ttile:focus-visible { outline: none; }
body.shell-v2 .ttile--poster:focus-visible .ttile-img, body.shell-v2 .ttile--article:focus-visible, body.shell-v2 .ttile--rank:focus-visible { box-shadow: inset 0 0 0 2px var(--accent); }
body.shell-v2 .ttile-img { position: relative; aspect-ratio: 3 / 4; border-radius: var(--r-md); overflow: hidden; background: var(--surface-1); }
body.shell-v2 .ttile-img img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.3s ease; }
@media (hover: hover) and (prefers-reduced-motion: no-preference) { body.shell-v2 .ttile--poster:hover .ttile-img img { transform: scale(1.03); } }
body.shell-v2 .ttile-img--empty { display: grid; place-items: center; }
body.shell-v2 .ttile-initial { width: 44px; height: 44px; display: grid; place-items: center; border-radius: var(--r-md); background: var(--surface-3); color: var(--ink-80); font-size: 18px; font-weight: 600; }
body.shell-v2 .ttile-rank { position: absolute; top: 8px; left: 8px; min-width: 24px; padding: 0 7px; border-radius: var(--r-pill); background: rgba(9, 9, 9, 0.72); color: #fff; font-size: 12px; font-weight: 600; line-height: 22px; text-align: center; font-variant-numeric: tabular-nums; }
body.shell-v2 .ttile-badge { position: absolute; top: 8px; right: 8px; padding: 0 7px; border-radius: var(--r-pill); background: #ffffff; color: #000; font-size: 11px; font-weight: 600; line-height: 20px; }
body.shell-v2 .ttile-play, body.shell-v2 .ttile-ext { position: absolute; right: 8px; bottom: 8px; width: 26px; height: 26px; display: grid; place-items: center; border-radius: var(--r-pill); background: rgba(9, 9, 9, 0.72); color: #fff; }
body.shell-v2 .ttile-title { margin-top: 8px; font-size: var(--fs-fine); font-weight: 500; color: var(--ink); line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; word-break: keep-all; overflow-wrap: anywhere; }
body.shell-v2 .ttile-sub { margin-top: 2px; font-size: 12px; color: var(--ink-48); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }

body.shell-v2 .ttile--article, body.shell-v2 .ttile--rank { gap: 8px; padding: 16px; border-radius: var(--r-lg); background: var(--surface-1); transition: background-color 0.15s ease; }
body.shell-v2 .ttile--article:hover, body.shell-v2 .ttile--rank:hover { background: var(--surface-2); }
body.shell-v2 .ttile--article { min-height: 188px; }
body.shell-v2 .ttile-meta { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; color: var(--ink-48); }
body.shell-v2 .ttile-source { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.shell-v2 .ttile-date { flex: none; font-variant-numeric: tabular-nums; }
body.shell-v2 .ttile-headline { font-size: var(--fs-body); font-weight: 600; line-height: 1.4; letter-spacing: var(--ls-body); color: var(--ink); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; word-break: keep-all; overflow-wrap: anywhere; }
body.shell-v2 .ttile-summary { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink-80); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; word-break: keep-all; overflow-wrap: anywhere; }
body.shell-v2 .ttile-tags { display: flex; gap: 4px; flex-wrap: wrap; margin-top: auto; }
body.shell-v2 .ttile-tag { padding: 0 8px; border-radius: var(--r-pill); background: var(--surface-3); color: var(--ink-80); font-size: 11.5px; line-height: 20px; }
body.shell-v2 .ttile-ranklist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
body.shell-v2 .ttile-rankrow { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center; gap: 10px; }
body.shell-v2 .ttile-ranknum { font-size: 15px; font-weight: 600; color: var(--ink); text-align: right; font-variant-numeric: tabular-nums; }
body.shell-v2 .ttile-rankbody { display: flex; flex-direction: column; min-width: 0; }
body.shell-v2 .ttile-ranktitle { font-size: var(--fs-fine); font-weight: 500; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.shell-v2 .ttile-rankplat { font-size: 12px; color: var(--ink-48); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.shell-v2 .ttile-rankval { font-size: 12.5px; color: var(--ink-80); font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (max-width: 810px) {
  body.shell-v2 .trow-track { grid-auto-columns: 42%; margin-inline: calc(-1 * var(--sp-md)); padding-inline: var(--sp-md); scroll-padding-inline: var(--sp-md); }
  body.shell-v2 .trow-track:has(> .ttile--article), body.shell-v2 .trow-track:has(> .ttile--rank) { grid-auto-columns: 84%; }
  body.shell-v2 .trow-arrow { display: none; }
  body.shell-v2 .btn.trow-more, body.shell-v2 .btn.trow-reroll, body.shell-v2 .trow-seg-btn { min-height: 44px; line-height: 40px; }
}

/* ═══ 최근 영상(2026-09-16 오너 지시) — 영상(=캔버스) 하나가 카드 하나, 그 안에 클립 타일이 선다 ═══
 *   ⚠️ 이 블록의 클래스는 전부 새것이라 shell2.css 에 짝이 되는 무스코프 기본값이 **없다**(다른 .hub-* 와 다르다).
 *      섹션이 새 셸에서만 마운트되므로(hubHome 뷰) 여기 한 곳에서 레이아웃까지 소유한다 — 두 파일로 가르면
 *      기본값과 테마가 갈라져 한쪽만 낡는다.
 *   파랑은 포커스에만(DESIGN.md · Framer) · 카드는 표면 단 · 타일 테두리 없음. */
/* 정렬 행(justified rows) 판 — 타일이 전부 absolute 라 컨테이너는 좌표계만 준다.
 *   ⚠️ `height` 는 JS 가 박는다(`renderClipGrid`) — 여기서 주면 클립 수에 따라 틀린다.
 *   ⚠️ 행을 CSS grid 로 짤 수 없다: 타일 폭이 행마다 다르고(가로 클립이 세 열 몫을 먹는다) 그 값이
 *      비율에서 풀려 나오는 실수라, 열 템플릿으로 표현되지 않는다(`clipGrid.js` 머리주석). */
body.shell-v2 .hub-grid { position: relative; width: 100%; }
/* 뼈대(renderRecentVideos) — 실제 클립 타일(.hub-clip)과 같은 최소폭·간격·세로 비율(9:16, clipGrid.js MIN_COL_W·GAP). */
/* 열 수 상한 MAX_COLS=5 도 따른다(clipGrid.js) — 최소폭만 주면 넓은 화면에서 6열이 선다 */
body.shell-v2 .skel-hub-videos { --skel-min: max(168px, calc((100% - 40px) / 5)); --skel-gap: 10px; --skel-aspect: 9 / 16; --skel-thumb-r: var(--r-md); --skel-card-pad: 0; }
/* 타일 껍데기 — 위치·크기는 인라인 스타일이 정하고, CSS 는 **어떻게 움직이는가**만 정한다.
 *   원본(React Bits)이 gsap 으로 하던 x/y/w/h·opacity·blur 트윈이 이 한 묶음이다. */
body.shell-v2 .hub-grid-item {
  position: absolute; top: 0; left: 0; opacity: 0;
  /* 🔴 `translate` 는 `transform` 의 **별개 롱핸드**다 — 목록에 안 적으면 아래 24px 상승이 전이 없이 툭
   *   끊긴다(브라우저 실측: `is-in` 이 붙는 프레임에 실행 중인 전이가 transform·opacity 뿐이었다). */
  transition: transform 0.6s var(--ease), translate 0.6s var(--ease),
              width 0.6s var(--ease), height 0.6s var(--ease),
              opacity 0.8s var(--ease), filter 0.8s var(--ease);
  will-change: transform;
}
body.shell-v2 .hub-grid-item.is-in { opacity: 1; }
/* 들어오기 전에는 제자리보다 조금 아래에서 흐리다 — 원본 `animateFrom: 'bottom'` + `blurToFocus` 다.
 *   화면 밖(window.innerHeight + 200)에서 날아오게 하지 않는 것은 타일이 60개까지 서기 때문이다.
 *
 * 🔴 **`blur` 는 처음에 뺐다가 오너 지시("문제 없다면 켜")로 재 보고 켰다.** 뺐던 사유는 "`<video>`
 *   수십 개에 필터를 걸면 타일마다 합성 레이어가 선다" 였고 그건 **측정 없는 추측**이었다. 실측하니
 *   절반은 맞고 절반은 틀렸다 — 비용은 **타일 총수가 아니라 동시에 흐린 타일 수**에 붙는다.
 *
 *   실측(로컬 목 · 2× DPR · 상한인 60타일 · 프레임 간격 표본):
 *     · 60개를 **전부** 흐리게 하면: >25ms 프레임 6개 · 최악 74.6ms(끈 상태는 0개 · 25ms).  ← 실제로 튄다
 *     · **보이는 13개만** 흐리게(현재): 워밍업 후 4회 교차 — 켠 쪽 1/32·0/21·0/15·1/50,
 *       끈 쪽 5/42·0/16·0/19·0/18(>25ms개수/최악ms). 분포가 겹치고 최악 표본이 **끈 쪽**에서 나왔다.
 *   그래서 성립 조건은 다음 셋이고, 하나라도 깨면 다시 튄다:
 *     ① **화면 밖 타일은 아예 연출하지 않는다**(`recentVideos.js` — 이게 위 차이를 만든 장치다).
 *     ② **등장 동안만 걸린다.** `is-in` 이 붙으면 `filter: none` 이라 상주 필터가 아니다.
 *     ③ `will-change` 에 `filter` 를 **넣지 않았다.** 넣으면 등장이 끝난 뒤에도 레이어가 남는다.
 *   ⚠️ 반경은 비용과 상관이 없었다(2·4·8px 를 훑었는데 순서가 뒤죽박죽이다 — 튀는 것은 레이어가 서는
 *      첫 프레임이지 반경이 아니다). 원본 10px 대신 8px 인 것은 취향이다.
 *   ⚠️ 이 한 줄이 끄는 스위치다 — 느려 보이면 여기부터 지워 보면 된다. */
body.shell-v2 .hub-grid-item:not(.is-in) { translate: 0 24px; filter: blur(8px); }
/* 🔴 첫 배치는 전이 없이 **자리에 앉힌다.** ResizeObserver 는 레이아웃 뒤에 비동기로 오므로 그때는 이미
 *   `transform: none` 으로 스타일이 한 번 계산돼 있다 — 그 상태에서 좌표를 주면 모든 타일이 컨테이너
 *   **좌상단에서 날아온다**(브라우저 실측으로 확인). 등장은 상승 + 페이드이지 대각선 비행이 아니다. */
body.shell-v2 .hub-grid--settling .hub-grid-item { transition: none; }
@media (prefers-reduced-motion: reduce) {
  body.shell-v2 .hub-grid-item { transition: opacity 0.2s linear; }
  body.shell-v2 .hub-grid-item:not(.is-in) { translate: none; filter: none; }
}

/* 클립 타일 — 껍데기를 꽉 채운다(높이는 메이슨리가 `폭 × 비율` 로 정했다) */
body.shell-v2 .hub-clip { position: relative; width: 100%; height: 100%; border-radius: var(--r-md); overflow: hidden; background: var(--surface-2); }
body.shell-v2 .hub-clip-video { display: block; width: 100%; height: 100%; object-fit: cover; }
@media (hover: hover) {
  body.shell-v2 .hub-grid-item:hover .hub-clip { transform: scale(0.97); transition: transform var(--t-fast) var(--ease); }
}
body.shell-v2 .hub-clip--nourl { display: flex; align-items: center; justify-content: center; }
body.shell-v2 .hub-clip-none { padding: 0 10px; text-align: center; color: var(--ink-48); font-size: var(--fs-fine); line-height: 1.35; }
/* 자막 층 — 영상 **위에** 겹친다. 아래 붙이면 타일 높이가 비율과 달라져 열 쌓기가 어긋난다. */
body.shell-v2 .hub-clip-cap {
  position: absolute; inset: auto 0 0 0; display: flex; flex-direction: column; gap: 1px;
  padding: 22px 10px 8px; pointer-events: none;
  background: linear-gradient(to top, color-mix(in srgb, var(--canvas) 88%, transparent), transparent);
}
/* 11 → 12px: 타입 눈금의 최소는 12 다(그 아래는 마이크로 라벨 전용). 2026-09-18 감사에서 이 둘만 11 이었다. */
body.shell-v2 .hub-clip-owner { font-size: 12px; font-weight: 500; color: var(--ink-48); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.shell-v2 .hub-clip-owner.is-link { position: relative; pointer-events: auto; cursor: pointer; align-self: flex-start; max-width: 100%; border-radius: var(--r-xs); }
/* 이 줄은 글자이면서 버튼이다(role=button·Enter/Space — recentVideos.js). 글자 높이가 곧 과녁이라
 *   터치에서는 16px 남짓이었다. 시각은 그대로 두고 누르는 상자만 44 로 넓힌다.
 * 🔴 **여기서는 apple-rules ② 의 `::after` 수법을 못 쓴다.** 이 줄은 말줄임을 위해 `overflow: hidden` 이고
 *    자신이 `position: relative` 라 ::after 의 컨테이닝 블록이다 — 넓힌 만큼이 **그대로 잘린다**.
 *    브라우저 실측(같은 조건 재현): overflow hidden 이면 요소 위 10px 지점의 `elementFromPoint` 가 이 줄을 못 잡고,
 *    visible 이면 잡는다. 첫 판은 ::after 로 썼고 **아무 일도 하지 않았다**(독립 검증이 짚었다).
 *    그래서 패딩으로 제 상자를 키우고 같은 크기의 음수 마진으로 주변 배치를 되돌린다 — 제 패딩은 제 overflow 에 안 잘린다. */
@media (pointer: coarse) {
  /* ⚠️ 아래쪽 14px 가 `.hub-clip-name` 행을 덮는다. 그 행은 조상(`.hub-clip-cap`)에게서 `pointer-events: none` 을
   *    물려받아 히트 테스트가 통과하므로, **터치에서 클립 이름을 눌러도 캔버스가 열린다**(전엔 무동작).
   *    과녁이 커지는 쪽이라 의도에 어긋나지 않고, ▶(left:6px; top:6px)와도 겹치지 않는다(독립 검증이 짚었다). */
  body.shell-v2 .hub-clip-owner.is-link { padding-block: 14px; margin-block: -14px; }
}
body.shell-v2 .hub-clip-owner.is-link:hover { color: var(--ink); text-decoration: underline; }
body.shell-v2 .hub-clip-owner.is-link:focus-visible { outline: none; box-shadow: var(--ring-focus); color: var(--ink); }
body.shell-v2 .hub-clip-name { font-size: var(--fs-fine); font-weight: 500; color: var(--ink-80); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 만든 사람 — **남의 클립에만** 선다(서버가 그때만 ownerLabel 을 싣는다 · 2026-09-21 전 사용자 확장).
 *   작품명 줄과 같은 12px 최소 눈금이고, 위계는 색으로 가른다(작품명 48 · 이름 80 · 사람 48). */
body.shell-v2 .hub-clip-by { font-size: 12px; color: var(--ink-48); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 길이 배지 — 못 잰 클립에는 아예 없다(durationLabel 계약) */
/* 11 → 12px: 위와 같은 이유(타입 눈금 최소 12). */
body.shell-v2 .hub-clip-dur {
  position: absolute; right: 6px; top: 6px; padding: 0 6px; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--canvas) 70%, transparent); color: var(--ink);
  font-size: 12px; font-weight: 500; line-height: 18px; font-variant-numeric: tabular-nums; pointer-events: none;
}
/* ▶ — 쉬는 동안은 옅게 앉아 있고, 포인터를 올리거나 포커스·재생 중이면 또렷해진다.
 *   ⚠️ 터치에는 hover 가 없다 — `@media (hover: hover)` 로만 숨기고 기본은 보이는 상태로 둔다. */
body.shell-v2 .btn.hub-clip-play {
  position: absolute; left: 6px; top: 6px; min-height: 28px; width: 28px; padding: 0;
  border: 0; border-radius: var(--r-pill); color: var(--ink);
  background: color-mix(in srgb, var(--canvas) 62%, transparent);
  transition: opacity var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
body.shell-v2 .btn.hub-clip-play:hover { background: color-mix(in srgb, var(--canvas) 82%, transparent); }
body.shell-v2 .btn.hub-clip-play:focus-visible { outline: none; box-shadow: var(--ring-focus); opacity: 1; }
@media (hover: hover) {
  body.shell-v2 .btn.hub-clip-play { opacity: 0; }
  body.shell-v2 .hub-clip:hover .btn.hub-clip-play,
  body.shell-v2 .hub-clip.is-playing .btn.hub-clip-play { opacity: 1; }
}
/* 재생 중인 타일 — 파랑은 포커스 전용이라 테두리가 아니라 **밝기**로 말한다 */
body.shell-v2 .hub-clip.is-playing { box-shadow: inset 0 0 0 1px var(--hairline); }
body.shell-v2 .hub-clip.is-playing .hub-clip-name { color: var(--ink); }
