d3ro-voice/design.md
Yun Chan a0abda7a5c
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Successful in 53s
ci / 모바일 린트·타입·Jest (push) Successful in 45s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 21s
ci / .NET API 서버 테스트 (push) Successful in 13s
deploy-site / deploy (push) Failing after 27s
ci / 워크스페이스 빌드 검증 (push) Successful in 36s
feat(site): prerender every locale and publish structured data, llms.txt and sitemaps
Crawlers received an empty client-rendered shell (107 characters of text);
most AI crawlers do not run JavaScript, so the product was invisible to them.

- Build renders each locale to static HTML (/ for Korean, /en/ … /vi/) with
  src/entry-server.tsx + scripts/prerender.mjs; the browser hydrates the same
  locale. The language menu links to those pages instead of switching in place.
- Per-locale head: title, description, canonical, hreflang (+ x-default /en/),
  Open Graph and X cards with a per-locale 1200x630 image.
- JSON-LD graph: Organization, WebSite, WebPage, SoftwareApplication (KRW
  offers, version, release date, download URL from the canonical sources),
  HowTo and FAQPage. No invented ratings.
- robots.txt (search, ai-input and ai-train allowed; /app, /api, invites
  excluded), sitemap.xml with language alternates, llms.txt and llms-full.txt
  generated from the same translations and canonical values.
- IndexNow key and a post-deploy ping (Bing, Naver, Yandex).
- A factual "at a glance" section and two FAQ answers (recognised languages,
  where data is stored) in all 10 languages.
- Icons, apple-touch-icon and web manifest; asset base is now absolute so
  sub-path pages load the same bundle.

Verified: 3.6k-7.7k characters of text per page, no hydration warnings,
no overflow at 320/1440 in six locales, axe 0, Lighthouse mobile SEO,
accessibility and best practices 100.
2026-09-27 10:45:05 +09:00

20 KiB
Raw Blame History

design.md — D3RO Voice 크로스플랫폼 디자인 결정 기록

2026-08-22 전면 타이포그래피 개편 ("타이포그래피 퍼스트 v3"). 다음 실행(사람이든 에이전트든)은 이 파일을 읽고 같은 결정을 이어간다.

브리프 3줄

  • 무엇을: 로컬 AI 음성 어시스턴트의 6개 표면(데스크톱 Electron / 관리자 / 웹앱 / 마케팅 사이트 / React Native 모바일 / 공유 DS 패키지)의 통합 타이포그래피·폰트 개편.
  • 누구에게: 한국어 기본 사용자(개발자·지식노동자). 도구는 하루 종일 켜져 있다.
  • 제약: 기존 스택 유지(MUI 7 / Tailwind 3 / RN StyleSheet), 기능 파괴 금지, 성능 예산(히어로까지 JS 150KB gzip, 이번 개편은 폰트 로드 정리로 감소).

컨셉 한 문장 · 프리셋 · 리스크

  • 한 문장: 볼드를 버리고 크기·행간·명도로 위계를 만드는 한국형 스위스 미니멀 — "정밀 계기판의 고요한 밤"에서 "조용히 읽히는 정밀 도구"로.
  • 프리셋: swiss-minimal(그리드와 침묵) × dark-instrument(계기판의 정확함).
  • 감수한 리스크 하나: 헤드라인 최대 웨이트를 500으로 내린 것. 라틴 마케팅 관례 (700-800 디스플레이)를 버리는 선택이지만, 한글 낱글자의 시각 밀도에서는 500+큰 크기+타이트 트래킹이 더 고급스럽다 (Apple 한국어 페이지가 같은 길).

한글 폰트 결정 (리서치 기반)

  • 웹/데스크톱: Pretendard Variable 단일 패밀리. 근거: Inter 기반 네오그로테스크로 한/영 조화(혼용 시 별도 조정 불필요), 크로스플랫폼 웨이트 일관성, 9단계+가변 폰트, 사실상 한국 UI 표준. SUIT(본문 특화)·Paperlogy(마케팅 특화)를 검토했으나 교체 이득 < 전환 비용.
  • 로딩 방식: 다이나믹 서브셋 + woff2 + preload(preconnect) — 2025-26 표준. 화면에 쓰인 글리프만 분할 로드한다. 기존 정적 pretendard.css 전체 로드를 폐기.
  • 모바일(RN): 시스템 폰트 스택. bare RN이라 커스텀 한글 폰트 번들링(2MB+ ttf, 네이티브 재빌드) 대신 iOS=SF Pro→Apple SD Gothic Neo, Android=Roboto→Noto Sans KR 자동 폴백. Apple HIG/Material 권장 표준. Menlo/monospace 전면 폐기 — Menlo에 한글 글리프가 없어 한/영 혼용 입력이 뒤섞이는 버그였음.
  • site: Plus Jakarta Sans 제거. 한+영 혼용 헤드라인에서 라틴/한글 스트로크 불일치. Pretendard가 Inter 기반이므로 라틴도 Pretendard로 통일(로딩 1종 감소).

타이포그래피 원칙 (W3C klreq / 요즘IT DS / KRDS 종합)

  1. 볼드 금지(700/800/900 → 0회). 최대 600(의미 강조 strong 전용), 헤드라인 500, 몸통 400, 라벨 500. 위계는 크기·행간·명도(text token)로.
  2. 한글 행간 1.5~1.7 (body 1.6), 헤드라인도 1.2 이상. 블록 글자의 숨 쉬는 공간.
  3. 자간 0 근처. 음수는 대형 헤드라인만 -0.01~-0.02em. 양수(+0.02~0.04em)는 라틴 대문자 라벨에만. 한글에 양수 트래킹 금지.
  4. 한글 최소 크기 11px. 8-9px 토큰(engrave/micro/nano)은 상향 (engrave/micro 10px, nano 9px — 라틴 전용 텔레메트리).
  5. 폰트 1패밀리 + 웨이트 3종(400/500/600). mono는 텔레메트리·코드·숫자에만.

토큰 변경 (SSOT: packages/ui/src/theme.ts d3roTypo v3)

토큰 v2 (구) v3 (신)
hero 32px/700/-0.5px/1.2 32px/500/-0.02em/1.3
title 24px/700/-0.3px/1.25 24px/500/-0.01em/1.35
value 20px/600/0/1.1 20px/500/0/1.2
heading 16px/600/0/1.4 16px/500/0/1.5
body 14px/400/0/1.55 14px/400/0/1.6
label 11px/600/0.08em/1.2 11px/500/0.04em/1.3
engrave 9px/700/1px 10px/500/0.06em/1.2
nano 8px/700/0.5px 9px/500/0.03em/1.15
  • MUI 기본값 동반 변경: h4/h5 700→500, h6/button/caption 600→500, body1 1.5→1.6, Chip/Tab/ListItemButton 600→500.
  • d3roFontSans에 한글 폴백 추가: Apple SD Gothic Neo, Noto Sans KR, Malgun Gothic.
  • RN 대응(packages/ui-native/theme.ts d3roNativeTypo) 동일 철학으로 동기화. label 10px/700/ls2 → 11px/500/ls0.4.
  • site(Tailwind) type scale: hero lh 1.14→1.22, ls -0.035em→-0.02em 등 한글 보정.

표면별 변경 요약

표면 변경
packages/ui (SSOT) d3roTypo v3, MUI 오버라이드 탈볼드, 한글 폰트 스택
packages/ui-native 타이포 v3, sans 스택, PhosphorText 텍스트 글로우 제거, PhysicalButton uppercase·과도 트래킹 제거, StatusBar 8px→10px
apps/desktop 보더리스: Win/Linux frame:false + 커스텀 TitleBar, macOS titleBarStyle:'hiddenInset' + 네이티브 교통신호(커스텀 컨트롤 숨김, 78px 여백). 기존 frame:true+커스텀 타이틀바 이중 크롬 버그 해소. 인라인 700×95/800×10/900×2 → 500/600, 팝업 CSS 동일
apps/admin Pretendard 이중 로드(link+@import)→다이나믹 서브셋 단일, 미사용 JetBrains Mono 로드 제거, 700×84→500/800×2→600, 프리셋(table/filter/badge/button) 600→500, strong→600 CSS
apps/web Pretendard 다이나믹 서브셋 로드 추가(기존 폰트 0로딩→OS 폴백 렌더 버그 수정), login 하드코딩 스택 교체, 700×24/800×13/900×2 스윕
site font-bold 13/semibold 37 → medium, Plus Jakarta 제거, 한글 type scale 보정, strong 600
apps/mobile-rn Menlo/monospace 전면 제거(입력창 18곳), TabBar 인셋 기반(52+insets.bottom)+라벨 9px mono→11px sans, LoginScreen 세이프에어리어 추가, 스택 화면 8곳 하단 인셋 Math.max(insets.bottom,24), letterSpacing 디-모노화, 700×3→500

표준 준수 노트

  • 모바일 세이프에어리어: 모든 화면 useSafeAreaInsets 기반. 탭 화면은 탭 바가 인셋을 담당, 스택 화면은 화면이 담당. 고정 padding(60/120) 잔여 없음.
  • iOS/macOS: 데스크톱 macOS는 hiddenInset(교통신호 유지 — HIG 준수). 모바일 iOS는 시스템 폰트+자동 폴백. KeyboardAvoidingView 플랫폼 분기 유지.
  • 웹: font-display: swap + preconnect. 웹뷰 폰트 이중 로드 제거.

성능 예산과 실측

  • 폰트: admin 2회 로드→1회, site 2패밀리→1패밀리+mono, web 0→1(다이나믹 서브셋).
  • site 빌드 실측: 360.53KB JS(gzip 111.04KB), CSS 40.37KB(gzip 7.81KB) — 예산 내.
  • 애니메이션 정책 변화 없음(transform/opacity만, 기존 유지).

의도적으로 하지 않은 것

  • 관리자 상태값 라벨은 대문자 유지. ACTIVE/DISABLED/PRO+ VIP/POST/PATCH 등은 코드 비교·API 값이라 문장형 변환 금지. 대신 페이지 부제(• 구분 설명문)와 사이드바 그룹 라벨·대시보드 카드 라벨은 문장형으로 전환했다(2026-08-22).
  • site의 CRT/crosshair CSS는 유지. 감사에서 '레거시'로 분류했으나 실사용 확인(HowItWorks·Privacy·Crosshair) — 제거하지 않는다.

2026-08-22 추가 완료 (2차: 브랜드 통일 + 오프시스템 정리)

  • 브랜드 색 통일 — amber → blue 완료. site(brand-amber 134곳→brand-blue, favicon/download.html/LED 시맨틱 포함), mobile-rn(다크 #3b82f6/pressed #2563eb/ onAccent 흰색 반전, 라이트 #2563eb), ui-native 레거시(#3b82f6). 근거: 데스크톱·웹·관리자 + 6개 테마가 이미 blue(Midnight Glass)인데 site/모바일만 구 CRT amber 시대 잔재였다. 이제 전 표면 단일 액센트.
  • apps/web 오프시스템 색 전면 토큰화. download(잔여 hex 0), login(잔여 0), chat-panel/mic-recorder/commands/billing/history/sidebar의 구 amber #ff5c35 37곳 + zinc 표면 90여 곳 → d3roPalette/--d3-* var로 교체. 6테마 연동. download·login은 이제 테마 반응형. on-accent 대비 보정(챗 전송 버튼 hover 흰색). --d3-tag-* CSS 변수를 SSOT buildCssVars에 추가(color-mix 틴트 파생용).
  • 관리자 라벨 문장형 전환. 페이지 부제 10곳(• 나열 → 쉼표 문장형, 약어 STT/LLM/SLA/RAG/VIP 보존), 사이드바 그룹·대시보드 카드 라벨 CSS uppercase 제거 (원본 문자열은 이미 문장형). 죽은 "Service Telemetry NOT CONNECTED" 패널 제거.
  • 관리자 globals.css :root 죽은 변수 제거. SSOT와 이름이 충돌하는 19개 로컬 변수(소비자 0) 삭제, body는 --d3-bg-app/--d3-text-primary으로 직결.
  • 모바일 커스텀 한글 폰트 번들링 — 최종 결정: 하지 않음(확정). Apple HIG와 Material 가이드가 모두 시스템 폰트를 권장하고, iOS Apple SD Gothic Neo / Android Noto Sans KR 폴백 품질이 이미 우수하며, 정적 ttf 번들(2웨이트 약 5-10MB)은 다이나믹 서브셋이 없는 RN에서 비용 대비 효과가 낮다. 이 항목은 미확정이 아니라 확정된 결정이다.

다음 실행을 위한 지침

  • 새 텍스트는 d3roTypo/d3roNativeTypo 토큰 또는 MUI 기본값만 쓴다. 인라인 fontWeight는 금지(예외: 조건부 400↔500 전환).
  • 한글 문자열에 letterSpacing 양수 값을 직접 넣지 않는다.
  • 11px 미만 텍스트를 추가하지 않는다 (라틴 텔레메트리 예외 하한 9px).

2026-09-22 Local Flow Intelligence extension

  • 기존 swiss-minimal × dark-instrument 방향과 token/typography/a11y 규칙을 그대로 적용한다. Flow Radar·Edit Friction·App Quality는 색만으로 상태를 구분하지 않고 텍스트 라벨과 수치를 함께 보인다.
  • Privacy Receipt은 dl의 정의/값 구조로 보존 기간과 실제 row count를 짝지어 읽게 한다. raw key 이벤트/키코드/콘텐츠 스트림 미저장과, 옵트인 학습 텍스트의 별도 보존 정책을 혼동시키지 않는다. personal_phrases는 나이 기반 자동 만료가 없고 개별 삭제·전체 삭제·동의 철회로 제거됨을 정확히 보인다.
  • suggestion overlay는 좁은 폭을 유지한다. Why This Suggestion은 한 줄의 provenance와 count만 보이며 raw memory evidence를 노출하지 않는다.

2026-08-24 광고 수익화 E2E RED 스위트 (652 시나리오)

  • 위치: apps/desktop/tests/red/ads/ (ads-mediation 370 / admob-ssv 142 / ad-settlement 140)
  • 계열: A 입찰 거버넌스 110 · B 크리에이티브 안전성 80 · C 노출·클릭 추적 70 · D 리워드 60 · E SSV 검증 142 · F 정산 원장 90 · G 설정 거버넌스 40 · H 엔진↔정산 60
  • 실측: GREEN 582 / RED 70 (전체 데스크톱 스위트 1,242개, 기존 590개 회귀 0)
  • RED 백로그(구현해야 수입 무결성 확보):
    1. SSV 리플레이 방지 60 — 동일 transaction_id 재검증·과거/미래 timestamp 수용(미구현)
    2. setConfig 검증 10 — 음수 rewardTokensAmount 허용(미검증)
  • 이식성 경고: admob-ssv의 derEcdsaSignatureToRaw는 Deno WebCrypto가 DER 서명을 반환한다는 동작에 의존. Node(≥22)는 RAW(r||s)를 반환한다 — Deno 업그레이드 시 서명 형식이 바뀌면 검증이 전면 실패할 수 있음. 테스트 픽스처는 RAW→DER 변환기로 실 AdMob 형식을 재현한다.

2026-08-24 RED→GREEN 완료 + 수익화 계획

  • admob-ssv.ts: 리플레이 방지(tx_id 25h 캐시) + 타임스탬프 신선도(±1h) 구현 → 엣지함수 재배포
  • AdMediationEngine.setConfig: 음수/0 이하 설정값 검증 추가
  • 광고 스위트 652/652 GREEN, 데스크톱 전체 1,242/1,242 GREEN, tsc 0
  • 수익화 로드맵: docs/monetization-plan.md (AdMob 프로덕션 ID 발급이 다음 관문)

2026-09-26 랜딩 사이트(site/) 정리 — 진실성 · 접근성 · 단순화

연장 경로(design.md 방향 유지: swiss-minimal × dark-instrument, 탈볼드, Pretendard). 레퍼런스 1′: superwhisper.com 실측 — 히어로 CTA 2개, 히어로 수치 없음, 가격 3단 "이전 티어 전부 +".

  • 브리프: 무엇을 = Windows 음성 받아쓰기 홍보 사이트 / 누구에게 = 한국어 기본 지식노동자·개발자, 목표 행동 = 다운로드 1순위 + 구독(Pro/Pro+) / 제약 = 10개 로케일 전부 번역, 기존 토큰 유지.
  • 확정한 사실(사용자): 가격 Free ₩0 · Pro ₩2,900/월 · Pro+ ₩8,900/월(원화, 월간만), 오픈 소스 주장 삭제(저장소 비공개).
  • 섹션: 히어로(예시 1개) → 기능 → 작동 방식 → 개인정보 → 요금 → 다운로드 → FAQ → 푸터. 레이아웃 패밀리: 분할(히어로) / 제목 고정+목록(기능·FAQ) / 4열 번호 행(작동 방식) / 사양서 dl(개인정보) / 카드 3장(요금) / 8:4 패널(다운로드).
  • 사실 SSOT: 단축키 = packages/core/src/keybinding.ts(오른쪽 Alt 누른 채), 사용량 = packages/core/src/constants.ts PREMIUM_MODEL_LIMITS, 가격·결제 URL = site/src/pricing.ts, 버전·게시일 = site/src/release.ts(version:sync 대상).
  • 토큰 변경: brand.blue-deep #1d4ed8 추가(주 버튼 hover). 주 버튼 채움은 blue-dark(흰 글자 5.2:1) — blue(3.7:1)는 본문 크기 버튼 글자에 AA 미달. 그림자는 chassis만, 애니메이션은 glow-pulse만 남김.
  • 컴포넌트 클래스: .btn/.btn-primary/.btn-secondary(최소 44px, 줄바꿈 허용), .kbd(sans — 한글 키 이름), .link, .skip-link.
  • 한글 줄바꿈 클래스는 keep-ko(:lang(ko)에서만 keep-all). Tailwind 내장 break-keep을 쓰지 말 것 — 중국어·일본어에서 줄바꿈 기회를 없애 320px에서 가로 넘침이 났다(실측).
  • 접근성: 본문 건너뛰기 링크, 섹션 aria-labelledby, 모바일 메뉴·언어 선택 aria-expanded+Esc+포커스 복귀, FAQ 디스클로저(aria-controls/region), 예시 상태 aria-live, 감소 모션(전역 CSS + 예시는 결과로 바로), 앵커 scroll-margin-top, 새 탭 링크 표기, <html lang>·title·description 로케일 추종.
  • 검증: 10개 로케일 × 320/390/1440 가로 넘침 0, axe(wcag2a/aa/21aa/22aa/best-practice, ko·ja) 위반 0, 390 터치 타깃 24px 미만 0, 페이지 길이 1440 10,536→5,062px / 390 14,753→7,826px, 빌드 JS 111.0→98.0KB gzip, CSS 7.8→5.2KB gzip.

의도적으로 하지 않은 것

  • 히어로 수치·상태 뱃지 없음. "3배 빠름", "10/10", "<1.2s", "시스템 정상 가동", "프로토콜" 등은 근거가 없어 삭제. 되살리지 말 것.
  • 경쟁사 비교표·광고/리워드 문구·SHA-256 계산기·릴리스 히스토리 없음. 계산기는 비교 기준이 없어 무의미했고, 무결성은 latest.yml 링크로 대신한다.
  • 설치 파일 크기 표기 없음. version:sync가 갱신하지 않는 값이라 다음 릴리스에 틀린 숫자가 된다. 게시일(동기화 대상)만 쓴다.
  • "인기" 뱃지·연간 요금·통화 토글·Team·14일 체험 없음. 결제 카탈로그·앱에 근거 없음.
  • 기능 카드에 FREE/PRO 태그 없음. 로컬 기능은 전부 free다(LicenseService.FEATURE_MIN_TIER).
  • 화자 분리·RAG를 대표 기능으로 내세우지 않음. 화자 구분은 추정 단계라 약속하지 않는다.
  • CRT 움직이는 스캔 효과 제거(정지 주사선만 히어로 예시 패널에 유지). 이전 기록의 "CRT CSS 유지"는 예시 패널 한 곳으로 좁혔다.
  • 미확정: 서버 가격 불일치·결제 URL 이원화 → 2026-09-26 Wave 3에서 해소(packages/core plan-catalog/web-urls 단일 정본, /app/billing 하나).

2026-09-26 추기 · 히어로 반복 예시(앱 녹음 캡슐 재현)

  • 사용자 요청: "작동 방식 보기" CTA 대신 우측에서 앱과 똑같은 예시가 계속 돌게. 히어로 CTA는 다운로드 1개로 줄였다.
  • site/src/components/AppLoopDemo.tsx — 메모 창 + 화면 아래 녹음 캡슐. 대기 → 오른쪽 Alt 누른 채 말하기(파형·경과 시간·부분 전사) → 처리 중(점근 진행 막대) → 커서 자리에 붙여넣기 → 다음 예시. 언어마다 예시 2개.
  • 수치는 앱 apps/desktop/src/renderer/popups/recording-tip과 같다: 막대 9개 cos 가중치, 100ms 갱신, 스무딩 0.5, 무작위 ±35%, 높이 2–28px, 막대별 색, 진행 막대 120px min(95, (1-1/(1+1.5t))*100)%. 캡슐 CSS 값은 앱 style.css를 옮겨 적었다 (Electron 팝업 CSS는 전역 리셋을 포함해 사이트에서 직접 import할 수 없다 — 값이 바뀌면 두 곳을 함께 고친다).
  • 접근성: 5초 넘게 움직이므로 일시정지 버튼(WCAG 2.2.2, aria-pressed). 화면 밖·숨은 탭에서 멈춤. 감소 모션은 정지 화면 한 장. 애니메이션은 aria-hidden, 화면 낭독용 설명문(demo.description)을 둔다. 중·일은 띄어쓰기가 없어 세 글자씩 나타난다.
  • 하지 않은 것: 실제 음성 녹음·마이크 권한 데모(브라우저에서 로컬 모델을 돌릴 수 없고, 가짜 인식은 진실성 위반).
  • 제거: 이전 버튼식 예시(DictationDemo), WaveBars, Crosshair, CRT 화면 CSS — 이전 기록의 "CRT CSS 유지"는 이로써 종료.
  • 부수: site/public/404.html(없는 경로가 첫 화면으로 떨어지던 SPA 폴백 종료), 법률 페이지의 옛 주황 강조색을 파랑으로.
  • 앱 쪽 발견(미수정): 녹음 캡슐의 "처리 중..."은 HTML에 한국어로 고정돼 있어 앱 언어를 따르지 않는다.

2026-09-27 추기 · 검색·AI 검색 최적화 (SEO + GEO)

  • 문제: 사이트가 클라이언트 렌더링이라 크롤러가 받는 HTML 본문이 107자였다(Googlebot·GPTBot·ClaudeBot·PerplexityBot·네이버 Yeti 동일). AI 크롤러 대부분은 JS를 실행하지 않는다.
  • 빌드 때 언어별 정적 HTML을 미리 그린다(site/scripts/prerender.mjs + src/entry-server.tsx, React hydrateRoot로 이어받음). 주소: 한국어 /, 나머지 /{en,ja,zh,es,fr,de,pt,ru,vi}/. 언어 선택은 해당 주소로 가는 링크(크롤링 가능). 자동 언어 리다이렉트는 하지 않는다(구글 권장).
  • <head>는 언어마다 title·description·canonical·hreflang(10+x-default=/en/)·Open Graph·X 카드. 구조화 데이터(JSON-LD @graph): Organization, WebSite, WebPage, SoftwareApplication(가격 3종 KRW·버전·게시일·다운로드 URL — 정본에서), HowTo(작동 방식 4단계), FAQPage. 가짜 평점(aggregateRating)은 넣지 않는다.
  • AI 검색용: /llms.txt(llmstxt.org 형식, 영어+한국어 요약·핵심 사실·주요 주소), /llms-full.txt(영·한 전문). 모두 i18n 번역과 정본 값에서 빌드 때 생성 — 사이트 문구와 어긋날 수 없다.
  • robots.txt: 전체 허용 + Content-Signal: search=yes, ai-input=yes, ai-train=yes(홍보 목적), /app/·/api/·/accept-invite/ 제외, sitemap 안내. Cloudflare의 AI 봇 차단·Bot Fight Mode는 꺼져 있음을 확인.
  • sitemap.xml: 언어별 페이지(xhtml:link 대체 언어)+법률 문서. IndexNow 키 파일(/5d886cca….txt)과 배포 후 알림(scripts/ci/ping-indexnow.mjs) — Bing(ChatGPT·Copilot 검색 기반)·네이버·Yandex.
  • 보이는 내용 보강: "한눈에 보기" 사실 요약 섹션(정의형 문장 8개, 버전·가격은 정본), FAQ 2개 추가(인식 언어 — 앱 설정의 자동/한·영·일·중, 데이터 저장 위치).
  • 공유·아이콘: 언어별 1200×630 공유 이미지(public/og/<lang>.png), 파비콘 PNG·apple-touch-icon·PWA 아이콘·manifest. 생성: npx vite-node site/scripts/render-brand-assets.ts(문구·로고가 바뀌면 다시 실행해 커밋).
  • 빌드 경로 base: '/'(언어 하위 경로에서도 자산 경로가 맞도록).
  • 검증: 본문 3,600~7,700자/언어, 하이드레이션 경고 0, 10개 언어 320/1440 넘침 0, axe 0, Lighthouse(모바일) SEO·접근성·권장사항 100.
  • 하지 않은 것: 언어별 별도 콘텐츠(번역 외 추가 페이지)·블로그, 가짜 후기·평점, 자동 언어 리다이렉트.