/** * 붙여넣기 아이템의 발행본 공통 조각. * * ★ 색과 서체를 박지 않는다. 전부 템플릿 토큰(`--tpl-*`, `seo/head.ts` 가 심는다)을 읽는다 — * 한때 이 파일이 갱지색·주(朱)잉크·간판체를 hex 와 폰트명으로 들고 있었고, 그 바람에 * 사장님이 템플릿을 바꿔도 아이템 섹션만 레트로로 남았다. 아이템은 레트로 전용 부품이 아니다. * ★ 인상은 빌더 캔버스와 같게 그린다. 다만 **인터랙션은 옮기지 않는다** — * 캔버스의 턴테이블은 '지금 한 곡'만 펴는데 그러면 나머지 곡의 문장이 HTML 에 없다. * 이 사이트의 존재 이유가 AI·검색의 인용이라, 발행본은 전 항목을 펴고 가로로만 민다. */ import type {ReactNode} from 'react'; import type {DataSource, DataVerified} from '@o2o/shared'; import './items.css'; /** 카드·종이 면. 섹션 바탕보다 한 단계 앞이다. */ export const ITEM_CARD = 'var(--tpl-card, #fafafa)'; /** 선·테두리. */ export const ITEM_BORDER = 'var(--tpl-border, #d6d3d1)'; /** * 강조 — 연표의 전환점, 일력의 날짜, 소인 도장. * * ★ 강조색을 그대로 쓰지 않고 둘레 글자색을 섞는다(빌더 캔버스와 같은 규칙). 팔레트에 따라 * accent 가 바탕과 같은 밝기면 큰 숫자와 배지가 화면에서 사라진다. */ export const ITEM_ACCENT = 'color-mix(in oklab, var(--tpl-accent, #2563eb) 70%, currentColor)'; /** 어두운 면(필름·플립보드). */ export const ITEM_INVERSE = 'var(--tpl-inverse, #1c1917)'; /** 어두운 면 위의 글자색 — 흰색을 박지 않고 템플릿의 밝은 면 색을 쓴다. */ export const ITEM_INVERSE_INK = 'var(--tpl-bg, #ffffff)'; /** 글자색. */ export const ITEM_INK = 'var(--tpl-text, #09090b)'; /** * 아이템 섹션 껍데기. * * 바탕은 `--tpl-surface`(섹션 바탕) 또는 `--tpl-inverse`(어두운 아이템)다. * 다른 섹션들이 쓰는 `border-black/8` 대신 토큰 테두리를 쓴다 — 어두운 면에서 검은 선은 안 보인다. */ export function ItemSection({ id, name, subtitle, count, children, dark, }: { id: string; name: string; subtitle?: string; /** 각주 한 줄("총 8곡 · 확인 필요 2"). 없으면 각주를 그리지 않는다. */ count?: string; children: ReactNode; dark?: boolean; }) { return (

{name}

{subtitle &&

{subtitle}

}
{children}
{count &&

{count}

}
); } /** 가로로 미는 레일. 화살표 버튼은 두지 않는다 — 스크립트 없이도 손가락·트랙패드로 민다. */ export function Rail({children, label}: {children: ReactNode; label: string}) { return (
{children}
); } /** * 출처 한 줄 + 확신 배지. * * ★ 확인되지 않은 값을 숨기지 않고 드러낸다 — 발행본에서도 같다. 손님이 그 문장을 * 그대로 옮겨 적을 수 있어서, 어디까지가 확인된 것인지 화면이 말해야 한다. * ★ 배지 색만은 토큰이 아니다. '확인/확인필요'는 디자인이 아니라 신호라, 팔레트를 따라가다 * 경고가 안 보이는 색이 되면 안 된다. */ export function SourceLine({source, verified}: {source?: DataSource; verified?: DataVerified}) { if (!source && !verified) return null; return (

{verified && ( {verified} )} {source?.name && ( 출처 ·{' '} {source.url ? ( {source.name} ) : ( source.name )} )}

); } /** "총 N개 · 확인 필요 M" 각주. 단위(곡·장·명)는 아이템마다 다르다. */ export function tally(total: number, unverified: number, unit: string): string { return `총 ${total}${unit}${unverified > 0 ? ` · 확인 필요 ${unverified}${unit}` : ''}`; }