diff --git a/docs/AI_DISCOVERY_FRONTEND_HANDOVER.md b/docs/AI_DISCOVERY_FRONTEND_HANDOVER.md
new file mode 100644
index 0000000..05e36b7
--- /dev/null
+++ b/docs/AI_DISCOVERY_FRONTEND_HANDOVER.md
@@ -0,0 +1,94 @@
+# AI Discovery · Frontend Handover
+
+2026-08-31 세션 산출물을 INFINITH 프로덕트(React)에 반영하기 위한 인수인계 문서.
+시안은 HTML로 확정됐고, 이 문서는 그것을 실제 라우트로 옮기는 데 필요한 전부를 담는다.
+
+---
+
+## 0. 다음 세션 실행 프롬프트 (복붙용)
+
+```
+docs/AI_DISCOVERY_FRONTEND_HANDOVER.md 를 읽고 AI Discovery 랜딩 페이지를 프로덕트에 구현해줘.
+
+- 시안: docs/reports/ai_discovery_landing.html (픽셀 기준, 이 파일이 정답)
+- 라우트: /discovery (index) 신규, 기존 /discovery/:id 리포트 페이지와 공존
+- 재사용: Navbar/Footer(BI 워드마크), ScoreRing, Solution.tsx의 루프 다이어그램 구조(AGDP → ABMR 워딩)
+- 데이터: src/data/discovery_viewclinic.ts + src/lib/discoveryScore.ts 실측값만 사용, 수치 하드코딩 금지
+- 카피 규칙: 존댓말 문장형 타이틀, 엠대시 금지, break-keep, AI 답변 목업에 "구축 후 목표 상태 예시" 라벨 유지, 연락처는 o2oteam@o2o.kr만
+- 완료 기준: npm run lint 통과, /discovery가 시안과 시각적으로 일치, 모바일 1열 폴백
+끝나면 git push + vercel --prod 까지.
+```
+
+---
+
+## 1. 이번 세션에서 만든 것
+
+| 산출물 | 경로 | 상태 |
+|---|---|---|
+| 랜딩 시안 (단일 HTML, 픽셀 정답) | `docs/reports/ai_discovery_landing.html` | 확정 |
+| 랜딩 스크린샷 (덱 삽입용) | `docs/reports/shots/land_hero.png`, `land_report.png` | 확정 |
+| POC 제안서 덱 (10장) | `docs/reports/Viewclinic_POC_Proposal.pptx` / `.pdf` | 확정 |
+| 덱 빌드 스크립트 | `docs/reports/build_viewclinic_poc_deck.py` | 재실행 가능 |
+
+시안 HTML은 외부 의존성이 Google Fonts(Playfair Display, Noto Sans KR) 하나뿐인
+단일 파일이다. 랜딩을 수정하면 스크린샷 재캡처 → 덱 리빌드 순서로 갱신한다
+(스크린샷 크롭 좌표는 배경색 감지로 잡았다: 히어로 0–1384px, 리포트 밴드는 #F4F6FB 행 범위).
+
+## 2. 구현 대상: 라우트와 페이지
+
+- 신규 라우트: `/discovery` → `DiscoveryLandingPage.tsx` (src/main.tsx에 추가)
+- 기존 라우트: `/discovery/:id` → `DiscoveryReportPage.tsx` (수정 불필요, 랜딩의 "샘플 리포트 보기"가 `/discovery/viewclinic`으로 연결)
+- Navbar/Footer는 전역 레이아웃이 이미 감싼다. 랜딩 시안의 자체 네비·푸터 마크업은 옮기지 말 것.
+
+## 3. 섹션 → 컴포넌트 매핑 (시안 위에서 아래 순서)
+
+| 시안 섹션 | 컴포넌트 제안 | 재사용/참조 |
+|---|---|---|
+| 히어로 (72px 헤드라인 + 배지 + CTA 2개) | `discovery/DiscoveryHero.tsx` | `Hero.tsx` 구조, `.text-gradient` |
+| ABMR 루프 카드 (다크) | `discovery/AbmrLoop.tsx` | **`Solution.tsx` 46–130행 다이어그램을 복제 후 워딩만 교체** |
+| AI 답변 목업 카드 | `discovery/AiAnswerMock.tsx` | glass-card |
+| Why Now 다크 밴드 (12종 / 0건 / 35~70%) | `discovery/WhyNow.tsx` | |
+| How It Works (Audit·Build·Prove) | `discovery/HowItWorks.tsx` | glass-card + EFE6FF 아이콘 스퀘어 |
+| 샘플 리포트 (48점 링 + A~F 바 + 칩 3장) | `discovery/SampleReport.tsx` | **`ScoreRing.tsx` 그대로**, 바 색상은 `getScoreColor()` 로직 재사용 |
+| 8주 로드맵 타임라인 | `discovery/Roadmap.tsx` | |
+| 정직성 인용문 + 3필 | `discovery/HonestyStrip.tsx` | |
+| 최종 CTA (다크) | `discovery/DiscoveryCta.tsx` | |
+
+## 4. 디자인 실측값 (시안에 이미 적용된 프로덕션 토큰)
+
+- **BI 워드마크**: Playfair Display Black(900) 30px, tracking 0.05em, `linear-gradient(to right, #4F1DA1, #021341)` 텍스트 클립. Navbar/Footer 컴포넌트에 이미 있으므로 재사용.
+- **DS Primary pill 버튼**: `px-10 py-4 text-lg font-medium rounded-full shadow-xl`, hero 변형은 `from-[#4F1DA1] to-[#021341]` + ArrowRight. `MultiChannelInput.tsx` 195행 참조.
+- **text-gradient**: `#0A1128 → #3b5998` 클립 (index.css `.text-gradient`).
+- **히어로 배경**: radial `#e0e7ff → #faf5ff → #fdf2f8` + 블롭 (Hero.tsx와 동일 계열).
+- **ScoreRing**: strokeWidth 8, 트랙 `#f1f5f9`, 점수 구간색 ≤40 `#C084CF` / ≤60 `#8B9CF7` / ≤80 `#7C6DD8` / 80+ `#6C5CE7`. 48점은 `#8B9CF7`.
+- **상태 칩**: index.css의 status-critical/warning/good/info 파스텔 토큰 (치명/보완/강점 칩).
+- **다크 카드**: `#0F1B45` bg + `#2A3A6B` border (Why Now 통계 카드).
+- 루프 회전 애니메이션: `animate-[spin_20s_linear_infinite]` (Solution.tsx와 동일).
+
+## 5. 데이터 연결
+
+- 수치는 전부 `src/data/discovery_viewclinic.ts`(`DISCOVERY_VIEWCLINIC`)와 `src/lib/discoveryScore.ts` 계산 결과에서 가져온다. **48, 92%, 20% 등을 JSX에 하드코딩하지 말 것** (재진단 시 점수가 바뀐다).
+- 카테고리 바 6개(A~F)는 discoveryScore의 카테고리별 pct를 순회 렌더.
+- 시안의 고정 수치는 2026-08-28 실측 스냅샷이다. 값이 다르게 나오면 데이터 쪽이 정답.
+
+## 6. 카피·정직성 규칙 (이번 세션에서 확정된 것)
+
+1. 타이틀은 존댓말 문장형으로 통일 ("~없습니다", "~올립니다"). 반말 종결·명사구 타이틀 금지.
+2. 엠대시 금지, 은유 금지, `break-keep` 적용 (기존 텍스트 스타일 규칙과 동일).
+3. AI 답변 목업에는 **"AEO / GEO 구축 후 목표 상태 예시" 라벨을 반드시 유지**한다. 현재 상태로 오인되게 만들지 않는다.
+4. 허위 도메인·이메일 금지. 연락처는 `o2oteam@o2o.kr` (Navbar 문의 mailto는 기존 `contact@o2o.kr` 유지).
+5. 시장 통계를 새로 만들어 넣지 않는다. 시안의 3개 수치(12종 크롤러 / 네이버 기여 0건 / Direct 유실 35~70%)는 근거 있는 실측·문헌 값이며, 이 밖의 수치 추가는 근거 확인 후에만.
+6. 순위·노출 보장형 문구 금지. "언급률과 신뢰구간으로 보고" 프레임 유지.
+
+## 7. 완료 기준·배포
+
+- `npm run lint` (tsc --noEmit) 통과.
+- 1440px에서 `/discovery`가 `docs/reports/ai_discovery_landing.html`과 시각적으로 일치.
+- 390px에서 2열 그리드가 1열로 접히고 헤드라인이 줄바꿈 깨짐 없이 표시.
+- 배포: `git push` 후 `vercel --prod` 수동 실행 (Gitea 자동 트리거 없음).
+
+## 8. 이후 백로그 (이번 구현 범위 아님)
+
+- 랜딩 CTA를 실제 진단 신청 폼/파이프라인(`discover-channels` Edge Function)에 연결.
+- 태하(법무법인, 68점 B) 케이스를 두 번째 샘플로 노출할지 결정.
+- 덱 스크린샷 자동 갱신 스크립트화 (랜딩 변경 → 캡처 → 크롭 → 덱 리빌드).
diff --git a/docs/reports/Viewclinic AI Discovery_POC_Proposal.pdf b/docs/reports/Viewclinic AI Discovery_POC_Proposal.pdf
new file mode 100644
index 0000000..7866c2d
Binary files /dev/null and b/docs/reports/Viewclinic AI Discovery_POC_Proposal.pdf differ
diff --git a/docs/reports/Viewclinic_AI Discovery_POC_Proposal.pptx b/docs/reports/Viewclinic_AI Discovery_POC_Proposal.pptx
new file mode 100644
index 0000000..9caead0
Binary files /dev/null and b/docs/reports/Viewclinic_AI Discovery_POC_Proposal.pptx differ
diff --git a/docs/reports/ai_discovery_landing.html b/docs/reports/ai_discovery_landing.html
new file mode 100644
index 0000000..723e1e6
--- /dev/null
+++ b/docs/reports/ai_discovery_landing.html
@@ -0,0 +1,401 @@
+
+
+
+
+
+INFINITH · AI Discovery AEO/GEO
+
+
+
+
+
+
+
+
+
+
+
+ INFINITH
+ AI DISCOVERY
+
+
+
+
+
+
+
+
+
+
+
+
+
+
AI DISCOVERY · AEO / GEO for Clinics
+
+
+
AI가 추천하는 병원은
따로 있습니다.
+
+
고객은 이제 ChatGPT와 Perplexity에게 병원을 묻습니다.
답변에 인용될 근거를 36개 항목으로 실측 진단하고, 8주 안에 구축합니다.
+
+
+
+
+
+
+
+
+
+
+
THE ENGINE · ABMR CYCLE
+
+
+
+
+
+ ABMR
+ Discovery
Loop
+
+
+
+
+
+
+
+
+
+
+
+
ABMR Cycle: 실측 진단(Audit) → 스키마·콘텐츠 구축(Build) → 언급률·AI 유입 측정(Measure) → 우선순위 재조정(Refine)의 무한 루프. 사이클이 돌 때마다 측정된 언급률이 다음 구축의 입력이 됩니다.
+
+
+
점수 48 → 61+
+
등급 C → B
+
언급률 상승 추적
+
+
+
+
+
+
THE OUTPUT · AI 답변 인용
+
+
+
강남에서 안전하게 가슴성형 상담받을 수 있는 병원 추천해줘
+
+
모티바 공식 KOL 의료진이 있는 뷰성형외과는 강남언니 후기 19,316건과 의료진 26명 정보가 공개되어 있으며, 시술별 FAQ와 비교표를 제공합니다.
+
+
viewclinic.com
+
gangnamunni.com
+
youtube.com
+
AEO / GEO 구축 후 목표 상태 예시
+
+
+
+
+ 루프 1회전(8주)마다 36개 항목을 재측정해 점수를 갱신합니다.
+
+
+
+ 질의 10개 × 5회 스팟체크로 인용 여부를 신뢰구간과 함께 보고합니다.
+
+
+
+ 측정 결과가 다음 사이클의 구축 우선순위를 자동으로 정합니다.
+
+
+
+
+
+
+
+
+
+
+
+
+
WHY NOW
+
검색은 끝났고, 답변이 시작됐습니다.
+
답변엔진은 광고 입찰이 아니라 크롤링 가능한 오픈웹 근거로 병원을 고릅니다. 인용되지 못하면 비교 대상에도 오르지 못합니다.
+
+
+
12종
+
AI 크롤러가 이미 읽는 중
+
GPTBot·OAI-SearchBot·PerplexityBot 등이 오늘도 병원 사이트를 수집합니다. 문제는 접근이 아니라 인용할 내용입니다.
+
+
+
0건
+
네이버 자산의 AI 기여
+
네이버 블로그·플레이스는 robots.txt로 AI 크롤러를 전면 차단합니다(2026-08 실측). 지금까지의 네이버 마케팅은 AI 답변에 닿지 않습니다.
+
+
+
35~70%
+
Direct로 숨는 AI 유입
+
AI가 보낸 방문의 상당수는 유입 경로 없이 도착합니다. 측정 인프라 없이는 이미 오는 환자도 세지 못합니다.
+
+
+
+
+
+
+
+
+
+
HOW IT WORKS
+
진단하고, 구축하고, 숫자로 증명합니다.
+
+
+
+
+
+
+ STEP 01
+ Audit
+
+
AEO/GEO 기준표 v1.0으로 36개 항목을 실측합니다. 크롤러 접근성부터 인용 가능 콘텐츠, 측정 인프라까지 6개 카테고리 100점 척도. 항목마다 근거 등급을 명시합니다.
+
+
+
+
+
+ STEP 02
+ Build
+
+
MedicalClinic·Physician 스키마를 심고, 흩어진 표면의 NAP을 한 글자까지 통일합니다. 핵심 페이지에 정의문과 시술별 FAQ를 넣어 AI가 잘라 갈 단락을 만듭니다.
+
+
+
+
+
+ STEP 03
+ Prove
+
+
GA4 AI 채널그룹과 서버로그 크롤러 검증을 설치하고, 질의 10개 × 5회 반복 스팟체크로 변화를 측정합니다. 단발 조회가 아니라 언급률과 신뢰구간으로만 보고합니다.
+
+
+
+
+
+
+
+
+
+
SAMPLE REPORT · 뷰성형외과 실측
+
읽을 수는 있지만, 인용할 근거가 없습니다.
+
실제 1차 진단 결과입니다. 어디가 강하고 어디가 비어 있는지, 점수는 카테고리 단위로 드러납니다.
+
+
+
+
+
+
+
+
+ 48
+ / 100
+
+
+
+
등급 C
+
AI Answer Readiness
+
+
36개 항목 중 31개 실측.
미검증 5개는 분모에서 제외했습니다.
+
+
+
+
+
+
+
+ A · AI 크롤러 접근성
+ 92%
+
+
+
+
+
+
+ B · 엔티티 식별·구조화
+ 30%
+
+
+
+
+
+
+ C · 답변 인용 가능 콘텐츠
+ 20%
+
+
+
+
+
+
+ D · AI 인용 표면 확보
+ 72%
+
+
+
+
+
+
+ E · AI 가시성 측정 인프라
+ 0%
+
+
+
+
+
+
+ F · 기술 위생
+ 33%
+
+
+
+
+
+
+
+
+
+
+
치명
+
AI가 누구인지 확정 못 함
+
MedicalClinic 스키마 없음. 시술 목록 JSON-LD는 문법 오류로 통째로 버려집니다.
+
+
+
보완
+
인용할 문장이 없음
+
정의문 0개, FAQ 0개, HTML 표 0개. 답변엔진이 잘라 갈 단락이 없습니다.
+
+
+
강점
+
표면 자산은 이미 강함
+
강남언니 후기 19,316건, GBP 4.9점. 홈페이지가 따라가면 바로 인용 근거가 됩니다.
+
+
+
+
+
+
+
+
+
+
+
8-WEEK ROADMAP
+
8주 안에, C에서 B로 올립니다.
+
+
+
+
+
+
+
W1–2
+
기반 설치
+
스키마·NAP·측정 인프라를 설치하고 기준선 스팟체크를 1회 측정합니다.
+
+
+
+
W3–6
+
콘텐츠 구조
+
정의문·FAQ·H1 재설계와 HTML 비교표를 구축하고 WordPress를 업그레이드합니다.
+
+
+
+
W7–8
+
재진단·보고
+
36개 항목을 재측정하고 미검증 5개를 반영해 총점을 다시 산정합니다.
+
+
+
+
+
+
+
+
+
+
"스키마를 넣으면 인용된다고 약속하지 않습니다.
인용될 수 없는 이유를 전부 제거하고, 결과를 반복 측정으로 증명합니다."
+
+
효과 없는 llms.txt는 팔지 않습니다
+
순위·노출을 보장하지 않습니다
+
단발 측정으로 보고하지 않습니다
+
+
+
+
+
+
+
+
AI가 당신의 병원을 인용하게 하세요.
+
진단은 무료입니다. 36개 항목 실측 리포트를 받아보고 결정하세요.
+
+
+ 무료 AEO / GEO 진단 받기
+
+
o2oteam@o2o.kr
+
+
+
+
+
+
+
+ INFINITH
+ © 2026 INFINITH. All rights reserved.
Infinite Marketing for Premium Medical Business & Marketing Agency
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/reports/build_viewclinic_poc_deck.py b/docs/reports/build_viewclinic_poc_deck.py
new file mode 100644
index 0000000..651e5f9
--- /dev/null
+++ b/docs/reports/build_viewclinic_poc_deck.py
@@ -0,0 +1,210 @@
+"""
+Viewclinic AI Discovery POC Proposal 덱 빌드 (INFINITH Deck 규격, 10페이지)
+
+제안 4장 + 진단 리포트 요약 5장 + 클로징 1장.
+본문 텍스트는 기본값 대비 +2pt (rows/bullets 18, grid 16.25, quote 19.25).
+
+ python3 docs/reports/build_viewclinic_poc_deck.py docs/reports/Viewclinic_POC_Proposal.pptx
+"""
+import sys
+sys.path.insert(0, "/Users/haewonkam/.claude/skills/infinith-deck/scripts")
+import ifd
+from ifd import Deck
+
+BODY = 18.0 # rows 본문·불릿 (기본 16 + 2)
+GRID = 16.25 # 스코어카드 격자 셀 (기본 14.25 + 2)
+QUOTE = 19.25 # 인용 본문 (기본 17.25 + 2)
+
+
+def quote_up(s, text, who, x=1.25, y=6.63, w=17.5, h=1.83):
+ """ifd.Slide.quote 와 동일 레이아웃, 본문만 +2pt."""
+ ifd._rect(s._raw, x, y, w, h, fill=ifd.LIGHT_BG if not s.dark else ifd.DARK_CARD, line=None, radius=0)
+ ifd._rect(s._raw, x, y, 0.04, h, fill=ifd.VIOLET, line=None, radius=0)
+ ifd._check(text, w - 0.5, QUOTE, 2, "quote")
+ ifd._write(ifd._tb(s._raw, x + 0.28, y + 0.25, w - 0.5, h - 0.9), text, QUOTE, ifd.SANS,
+ color=ifd.WHITE if s.dark else ifd.INK, line_spacing=1.2)
+ ifd._write(ifd._tb(s._raw, x + 0.28, y + h - 0.6, w - 0.5, 0.44), who, 18.0, ifd.SANS,
+ color=ifd.MUTED_DK if s.dark else ifd.MUTED)
+
+
+OUT = sys.argv[1] if len(sys.argv) > 1 else "Viewclinic_POC_Proposal.pptx"
+d = Deck()
+
+# 01 Cover
+d.cover(
+ eyebrow="PROOF OF CONCEPT · POC PROPOSAL",
+ title_lines=["AI Discovery,", "AEO / GEO."],
+ subtitle="뷰성형외과 × INFINITH · AI 답변엔진 노출 구축 검증 제안 · 2026. 08. 31",
+ meta_right="AI Discovery Proposal · 2026",
+)
+
+# 02 Why now (dark)
+s = d.slide(eyebrow="Why Now · AEO / GEO",
+ title="고객은 이미 AI에게 ", title_accent="병원을 묻습니다",
+ lede=["검색은 링크 목록에서 하나의 답변으로 바뀌었습니다.",
+ "답변에 인용되지 못한 병원은 비교 대상에도 오르지 못합니다."],
+ dark=True, section="Why Now")
+L, R = s.cards2(y=s.band_y)
+L.chip("시장의 변화", "info").rows([
+ ("검색이 답변으로 바뀌었다", "ChatGPT·Perplexity·네이버 AI 브리핑이 병원을 직접 추천. 클릭 없이 답변만 소비되는 비중이 커지는 중."),
+ ("AI는 인용으로만 노출한다", "답변엔진은 크롤링 가능한 오픈웹 근거를 골라 인용. 광고 입찰이 없어 근거를 먼저 갖춘 병원이 선점."),
+ ("네이버 자산은 기여 0", "네이버 블로그·플레이스는 robots.txt로 AI 크롤러를 전면 차단(2026-08 실측). 기존 네이버 마케팅이 AI 답변에 닿지 않음."),
+], b_size=BODY)
+R.chip("뷰성형외과 실측 · 48 / 100 · C", "critical").rows([
+ ("읽을 수는 있다", "robots.txt가 AI 검색봇 12종을 전부 허용. 크롤러 접근성 92%로 기술적 전제는 이미 확보."),
+ ("인용할 근거가 없다", "MedicalClinic 스키마 없음, FAQ 0개, 정의문 0개. 답변 인용 가능 콘텐츠 점수 20%."),
+ ("성과를 잴 수도 없다", "AI 유입 측정 인프라 0%. AI가 보내는 환자를 세는 장치 자체가 아직 없음."),
+], b_size=BODY)
+
+# 03 Offering — 랜딩 히어로 스크린샷 + 3스텝
+from pptx.util import Inches
+
+SHOT = "/Users/haewonkam/orca/workspaces/INFINITH/AEO-GEO-Audit/docs/reports/shots"
+s = d.slide(eyebrow="Our Offering · AI Discovery",
+ title="진단하고, 구축하고, ", title_accent="숫자로 증명합니다",
+ lede="AI Discovery는 36개 항목 실측 진단, 우선순위 구축, 반복 측정 보고까지 한 파이프라인으로 제공합니다.",
+ section="Offering")
+img_h = 9.9 - s.band_y
+img_w = img_h * (1440 / 1384)
+ifd._rect(s._raw, 1.25 - 0.05, s.band_y - 0.05, img_w + 0.10, img_h + 0.10,
+ fill="FFFFFF", line=ifd.CARD_LINE, radius=0.06)
+s._raw.shapes.add_picture(f"{SHOT}/land_hero.png", Inches(1.25), Inches(s.band_y), height=Inches(img_h))
+rx = 1.25 + img_w + 0.40
+R = ifd.Card(s, rx, s.band_y, 18.75 - rx, img_h, s.dark)
+R.rows([
+ ("STEP 1 · Audit", "AEO/GEO 기준표 v1.0으로 36개 항목 실측. 뷰성형외과 1차 진단 완료, 48점 등급 C."),
+ ("STEP 2 · Build", "스키마 삽입, NAP 통일, 정의문·FAQ 구축. AI가 인용할 근거를 홈페이지에 만듭니다."),
+ ("STEP 3 · Prove", "GA4·서버로그·반복 스팟체크 설치. 언급률과 신뢰구간으로만 보고합니다."),
+], b_size=BODY)
+
+# 04 What you get (KPI + quote)
+s = d.slide(eyebrow="What You Get · 8 Weeks",
+ title="8주 후, 이렇게 ", title_accent="달라집니다",
+ lede="목표는 두 가지입니다. 인용될 기술적 전제를 완성하고, AI가 보내는 환자를 처음으로 계측합니다.",
+ section="Outcome")
+k1, k2, k3, k4 = s.cards4(y=s.band_y, h=2.55)
+k1.kpi("48 → 61+", "AI ANSWER READINESS", "등급 C에서 B로")
+k2.kpi("100%", "AI 유입 측정 인프라", "현재 0%. GA4·서버로그 설치")
+k3.kpi("36 / 36", "검증 항목 커버리지", "미검증 5개를 계정 연동으로 해소")
+k4.kpi("19,316", "강남언니 후기", "기존 자산을 AI 인용 표면으로 연결")
+quote_up(s, "스키마를 넣으면 인용된다고 약속하지 않습니다. 인용될 수 없는 이유를 전부 제거하고, 결과를 반복 측정으로 증명합니다.",
+ "INFINITH 정직성 규약")
+
+# ── 진단 리포트 요약 5장 ──────────────────────────────────────────────────────
+
+# 05 Report 1 · Executive summary
+s = d.slide(eyebrow="Report 1 / 5 · Executive Summary",
+ title="읽을 수는 있지만, ", title_accent="인용할 근거가 없습니다",
+ lede=["36개 항목 중 31개를 실측했고, 오너 계정이 필요한 5개는 점수 분모에서 제외했습니다.",
+ "접근성(A) 92%와 표면(D) 72%는 양호. 엔티티(B) 30%와 콘텐츠(C) 20%가 총점을 끌어내립니다."],
+ section="Report · Summary")
+img_h = 9.9 - s.band_y
+img_w = img_h * (1440 / 912)
+ix = 1.25 + (17.5 - img_w) / 2
+ifd._rect(s._raw, ix - 0.05, s.band_y - 0.05, img_w + 0.10, img_h + 0.10,
+ fill="FFFFFF", line=ifd.CARD_LINE, radius=0.06)
+s._raw.shapes.add_picture(f"{SHOT}/land_report.png", Inches(ix), Inches(s.band_y), height=Inches(img_h))
+
+# 06 Report 2 · What AI sees (dark)
+s = d.slide(eyebrow="Report 2 / 5 · What AI Sees",
+ title="문제는 차단이 아니라 ", title_accent="내용입니다", dark=True, section="Report · Findings")
+L, R = s.cards2(y=s.band_y)
+L.chip("정상", "good").rows([
+ ("크롤러 접근 정상", "robots.txt가 OAI-SearchBot·PerplexityBot 등 12종을 허용. Cloudflare 차단 없음."),
+ ("서버 렌더링 본문", "WordPress가 본문을 서버에서 렌더. JS 없이 10,757자 추출."),
+ ("강남언니·YouTube 활발", "강남언니 후기 19,316건, 의료진 26명. YouTube 채널이 영상을 꾸준히 발행."),
+], b_size=BODY)
+R.chip("치명", "critical").rows([
+ ("누구인지 확정 불가", "MedicalClinic 스키마 없음, sameAs 비어 있음. 시술 목록 JSON-LD는 문법 오류로 무효."),
+ ("인용할 문장 없음", "H1 5개가 전부 영문 라벨. FAQ 0개, HTML 표 0개, 정의문 0개."),
+ ("표면 간 NAP 불일치", "GBP에 전화번호 없음. 웹사이트가 viewplasticsurgery.com. 주소 표기 3종."),
+], b_size=BODY)
+
+# 07 Report 3 · Scorecard
+s = d.slide(eyebrow="Report 3 / 5 · Scorecard · 6 Categories",
+ title="카테고리별 점수와 ", title_accent="검증 범위를 정리했습니다",
+ lede="점수는 검증된 항목만으로 정규화했습니다. 미검증 항목을 확인하면 총점이 바뀔 수 있습니다.",
+ section="Report · Scorecard")
+W, Rail = s.cards2(y=s.band_y, split=0.66)
+W.grid(
+ headers=["카테고리", "배점", "획득", "검증", "판정"],
+ col_w=[4.6, 1.3, 2.0, 1.4, 1.6],
+ rows=[
+ ["A · AI 크롤러 접근성", "20", "15.7 / 17", "6 / 7", "우수"],
+ ["B · 엔티티 식별·구조화", "20", "6.0 / 20", "7 / 7", "미흡"],
+ ["C · 답변 인용 가능 콘텐츠", "25", "5.0 / 25", "8 / 8", "치명"],
+ ["D · AI 인용 표면 확보", "20", "13.7 / 19", "5 / 6", "양호"],
+ ["E · AI 가시성 측정 인프라", "10", "0 / 2", "1 / 4", "미설치"],
+ ["F · 기술 위생", "5", "1.7 / 5", "4 / 4", "미흡"],
+ ],
+ tones=[[None, None, None, None, "info"], [None, None, None, None, "warning"],
+ [None, None, None, None, "critical"], [None, None, None, None, "good"],
+ [None, None, None, None, "critical"], [None, None, None, None, "warning"]],
+ row_h=0.62, size=GRID,
+)
+Rail.title("읽는 법").bullets([
+ "획득 분모는 검증 항목의 배점 합. E는 미검증 3개라 분모가 2점뿐.",
+ "총점 42.1 / 88 = 48점, 등급 C.",
+ "미검증 5개는 계정 접근으로 1일 안에 채웁니다.",
+], size=BODY)
+
+# 08 Report 4 · Action plan
+s = d.slide(eyebrow="Report 4 / 5 · Action Plan",
+ title="즉시 고칠 것과 ", title_accent="4주 안에 만들 것을 나눴습니다", section="Report · Action Plan")
+a, b, c = s.cards3(y=s.band_y)
+a.chip("P0 · 1일~1주", "critical").title("엔티티·표면·측정").bullets([
+ "깨진 JSON-LD 수정, MedicalClinic 스키마, sameAs 연결.",
+ "GBP 전화번호 등록, 웹사이트를 viewclinic.com으로 교정.",
+ "4개 표면 NAP를 한 글자까지 통일. 두 도메인 hreflang 연결.",
+ "GA4 AI 채널그룹, 서버로그 IP 검증, GSC·Bing 등록.",
+], size=BODY)
+b.chip("P1 · 4주 내", "warning").title("콘텐츠 구조").bullets([
+ "핵심 페이지 첫 단락에 정의문. 시술별 FAQ 5~8개.",
+ "페이지당 H1 1개 서술형. 비교표를 HTML table로.",
+ "WordPress 업그레이드, alt 누락 111개 보완, HTML 무게 절감.",
+], size=BODY)
+c.chip("P2 · 지속", "good").title("신선도·시딩").bullets([
+ "dateModified 마크업 활성화, 월 1회 사이트 갱신.",
+ "유튜브 영상을 사이트 글로 전사해 신선도를 전달.",
+ "티스토리·언론 기고로 naver.com 외 언급 도메인 확보.",
+ "분기 1회 반복 측정. 언급률과 신뢰구간으로만 보고.",
+], size=BODY)
+
+# 09 Report 5 · Roadmap (dark)
+s = d.slide(eyebrow="Report 5 / 5 · Roadmap · 8 Weeks",
+ title="8주 안에 ", title_accent="C에서 B로 올립니다", dark=True, section="Report · Roadmap")
+s.stats3([
+ ("W1–2", "스키마·NAP·측정 인프라 설치.\n기준선 스팟체크 1회."),
+ ("W3–6", "정의문·FAQ·H1 재설계·HTML 표.\nWordPress 업그레이드."),
+ ("W7–8", "재진단. 미검증 5개 반영해\n총점 재산정."),
+], y=s.band_y + 0.6)
+
+# 10 Closing — 제안서 포맷 (웹 CTA 버튼 없음, 다음 단계 3줄)
+raw = d._new(False)
+ifd._write(ifd._tb(raw, ifd.MARGIN, 2.12, ifd.CONTENT_W, 0.47), "NEXT STEP · POC PROPOSAL",
+ ifd.T_EYEBROW, ifd.SANS, bold=True, color=ifd.VIOLET, spc=300)
+_title = ["진단은 끝났습니다.", "8주로 증명하겠습니다."]
+_th = 96 / 72 * 1.2 * 2
+ifd._write(ifd._tb(raw, ifd.MARGIN, 2.97, ifd.CONTENT_W, _th), _title, 96.0, ifd.SANS,
+ bold=True, color=ifd.INK, line_spacing=0.98)
+_y = 2.97 + _th + 0.35
+ifd._write(ifd._tb(raw, ifd.MARGIN, _y, ifd.CONTENT_W, 0.62),
+ "8주 구축과 재진단으로 C를 B로 올리고, 전 과정을 숫자로 보고합니다.",
+ 27.75, ifd.SANS, color=ifd.BODY)
+_y += 1.10
+for num, label, desc in [
+ ("01", "검토", "본 제안서와 36개 항목 진단 리포트 전문을 검토합니다."),
+ ("02", "계정 연동", "GA4·서버로그·GSC·Bing 권한 공유 시 미검증 5개 항목을 1일 안에 확정합니다."),
+ ("03", "킥오프", "W1 스키마·NAP·측정 인프라 설치부터 착수합니다."),
+]:
+ ifd._write(ifd._tb(raw, ifd.MARGIN, _y, 0.75, 0.45), num, 19.5, ifd.SANS, bold=True, color=ifd.VIOLET)
+ ifd._write(ifd._tb(raw, ifd.MARGIN + 0.85, _y, 2.1, 0.45), label, 19.5, ifd.SANS, bold=True, color=ifd.INK)
+ ifd._write(ifd._tb(raw, ifd.MARGIN + 3.05, _y, 14.4, 0.45), desc, 19.5, ifd.SANS, color=ifd.BODY)
+ _y += 0.60
+ifd._write(ifd._tb(raw, ifd.MARGIN, ifd.Y_FOOTER - 0.1, 6.0, 0.6), "INFINITH", 30.0, ifd.SERIF,
+ bold=True, color=ifd.INK, spc=300)
+ifd._write(ifd._tb(raw, 11.0, ifd.Y_FOOTER, 7.75, 0.49), "o2oteam@o2o.kr", 20.25, ifd.SANS,
+ color=ifd.MUTED, align=ifd.PP_ALIGN.RIGHT)
+
+d.save(OUT)
+print("saved", OUT)
diff --git a/docs/reports/shots/land_hero.png b/docs/reports/shots/land_hero.png
new file mode 100644
index 0000000..86c47c8
Binary files /dev/null and b/docs/reports/shots/land_hero.png differ
diff --git a/docs/reports/shots/land_report.png b/docs/reports/shots/land_report.png
new file mode 100644
index 0000000..f72cd75
Binary files /dev/null and b/docs/reports/shots/land_report.png differ
diff --git a/src/components/discovery/AbmrLoop.tsx b/src/components/discovery/AbmrLoop.tsx
new file mode 100644
index 0000000..9805182
--- /dev/null
+++ b/src/components/discovery/AbmrLoop.tsx
@@ -0,0 +1,112 @@
+/**
+ * ABMR 루프 카드 (다크) — 히어로 좌측.
+ * Solution.tsx의 원형 루프 다이어그램 구조를 복제하고 워딩만 AGDP → ABMR로 교체.
+ * 점수·등급 칩은 discoveryScore 계산값에서만 받는다 (하드코딩 금지).
+ */
+import { motion } from 'motion/react';
+import type { OverallScore } from '../../types/discovery';
+
+const NODES = [
+ { letter: 'A', label: 'Audit', pos: 'top-0 left-1/2 -translate-x-1/2 -translate-y-1/2' },
+ { letter: 'B', label: 'Build', pos: 'top-1/2 right-0 translate-x-1/2 -translate-y-1/2' },
+ { letter: 'M', label: 'Measure', pos: 'bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2' },
+ { letter: 'R', label: 'Refine', pos: 'top-1/2 left-0 -translate-x-1/2 -translate-y-1/2' },
+];
+
+interface AbmrLoopProps {
+ overall: OverallScore;
+ /** 다음 등급 목표 (A등급이면 null) */
+ target: { score: number; grade: OverallScore['grade'] } | null;
+}
+
+export default function AbmrLoop({ overall, target }: AbmrLoopProps) {
+ const chips = [
+ ...(target
+ ? [`점수 ${overall.score} → ${target.score}+`, `등급 ${overall.grade} → ${target.grade}`]
+ : [`점수 ${overall.score} 유지 검증`, `등급 ${overall.grade} 유지 검증`]),
+ '언급률 상승 추적',
+ ];
+
+ return (
+
+ {/* Background glow */}
+
+
+
+ THE ENGINE · ABMR CYCLE
+
+
+ {/* Circular loop diagram (Solution.tsx 구조, ABMR 워딩) */}
+
+ {/* Animated glowing ring */}
+
+
+ {/* Center orb */}
+
+
+ ABMR
+
+
+ Discovery
Loop
+
+
+
+ {/* Nodes */}
+ {NODES.map((node) => (
+
+
+ {node.letter}
+
+
{node.label}
+
+ ))}
+
+ {/* Citation feedback curved text (M → A) */}
+
+
+
+ {/* Cycle description */}
+
+
+ ABMR Cycle: 실측 진단(Audit) → 스키마·콘텐츠
+ 구축(Build) → 언급률·AI 유입 측정(Measure) → 우선순위 재조정(Refine)의 무한 루프.
+ 사이클이 돌 때마다 측정된 언급률이 다음 구축의 입력이
+ 됩니다.
+
+
+
+ {/* Outcome chips — 계산값 파생 */}
+
+ {chips.map((chip) => (
+
+ {chip}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/discovery/AiAnswerMock.tsx b/src/components/discovery/AiAnswerMock.tsx
new file mode 100644
index 0000000..9be38ab
--- /dev/null
+++ b/src/components/discovery/AiAnswerMock.tsx
@@ -0,0 +1,77 @@
+/**
+ * AI 답변 목업 카드 — 히어로 우측.
+ * ⚠️ 정직성 규약: 이 카드는 실제 AI 답변이 아니라 "구축 후 목표 상태 예시"다.
+ * 라벨을 절대 제거하지 않는다 (현재 상태로 오인 금지).
+ */
+import { motion } from 'motion/react';
+
+const CITED_DOMAINS = ['viewclinic.com', 'gangnamunni.com', 'youtube.com'];
+
+interface AiAnswerMockProps {
+ clinicName: string;
+ /** 기준표 항목 수 (재측정 문구에 사용) */
+ criteriaCount: number;
+}
+
+export default function AiAnswerMock({ clinicName, criteriaCount }: AiAnswerMockProps) {
+ const protocolPoints = [
+ `루프 1회전(8주)마다 ${criteriaCount}개 항목을 재측정해 점수를 갱신합니다.`,
+ '질의 10개 × 5회 스팟체크로 인용 여부를 신뢰구간과 함께 보고합니다.',
+ '측정 결과가 다음 사이클의 구축 우선순위를 자동으로 정합니다.',
+ ];
+
+ return (
+
+
+ THE OUTPUT · AI 답변 인용
+
+
+ {/* 질의 */}
+
+
+
+ 강남에서 안전하게 가슴성형 상담받을 수 있는 병원 추천해줘
+
+
+
+ {/* 목표 상태의 답변 예시 */}
+
+ 모티바 공식 KOL 의료진이 있는{' '}
+
+ {clinicName}
+
+ 는 강남언니 후기 19,316건과 의료진 26명 정보가 공개되어 있으며, 시술별 FAQ와 비교표를
+ 제공합니다.
+
+
+ {/* 인용 도메인 칩 + 목표 상태 라벨 (제거 금지) */}
+
+ {CITED_DOMAINS.map((domain) => (
+
+ {domain}
+
+ ))}
+
AEO / GEO 구축 후 목표 상태 예시
+
+
+ {/* 측정 프로토콜 */}
+
+ {protocolPoints.map((point) => (
+
+
+ {point}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/discovery/DiscoveryCta.tsx b/src/components/discovery/DiscoveryCta.tsx
new file mode 100644
index 0000000..588b83b
--- /dev/null
+++ b/src/components/discovery/DiscoveryCta.tsx
@@ -0,0 +1,44 @@
+/**
+ * 최종 CTA (다크). 연락처는 o2oteam@o2o.kr 단일 채널 (허위 연락처 금지).
+ */
+import { motion } from 'motion/react';
+import { buildDiscoveryMailto, DISCOVERY_CONTACT_EMAIL } from './discoveryContact';
+
+interface DiscoveryCtaProps {
+ criteriaCount: number;
+}
+
+export default function DiscoveryCta({ criteriaCount }: DiscoveryCtaProps) {
+ return (
+
+
+
+ AI가 당신의 병원을 인용하게 하세요.
+
+
+ 진단은 무료입니다. {criteriaCount}개 항목 실측 리포트를 받아보고 결정하세요.
+
+
+
+
+ );
+}
diff --git a/src/components/discovery/DiscoveryHero.tsx b/src/components/discovery/DiscoveryHero.tsx
new file mode 100644
index 0000000..938a7d7
--- /dev/null
+++ b/src/components/discovery/DiscoveryHero.tsx
@@ -0,0 +1,107 @@
+/**
+ * AI Discovery 랜딩 히어로.
+ * Hero.tsx의 배경·배지·헤드라인 구조를 따르고, 하단에 ABMR 루프 + AI 답변 목업 2열 그리드.
+ * 시안: docs/reports/ai_discovery_landing.html (픽셀 기준)
+ */
+import { Link } from 'react-router';
+import { motion } from 'motion/react';
+import { ArrowRight } from 'lucide-react';
+import type { OverallScore } from '../../types/discovery';
+import AbmrLoop from './AbmrLoop';
+import AiAnswerMock from './AiAnswerMock';
+import { buildDiscoveryMailto } from './discoveryContact';
+
+interface DiscoveryHeroProps {
+ overall: OverallScore;
+ target: { score: number; grade: OverallScore['grade'] } | null;
+ clinicName: string;
+ sampleReportPath: string;
+ criteriaCount: number;
+}
+
+export default function DiscoveryHero({
+ overall,
+ target,
+ clinicName,
+ sampleReportPath,
+ criteriaCount,
+}: DiscoveryHeroProps) {
+ return (
+
+ {/* Decorative blobs */}
+
+
+
+
+ {/* Badge */}
+
+
+ AI DISCOVERY · AEO / GEO for Clinics
+
+
+ {/* Headline */}
+
+ AI가 추천하는 병원은
+
+ 따로 있습니다.
+
+
+ {/* Sub copy */}
+
+ 고객은 이제 ChatGPT와 Perplexity에게 병원을 묻습니다.
+
답변에 인용될 근거를 {criteriaCount}개 항목으로 실측
+ 진단하고, 8주 안에 구축합니다.
+
+
+ {/* CTAs */}
+
+
+ 무료 AEO / GEO 진단 받기
+
+
+
+ 샘플 리포트 보기
+
+
+
+ {/* ABMR loop + AI answer mock */}
+
+
+
+ );
+}
diff --git a/src/components/discovery/HonestyStrip.tsx b/src/components/discovery/HonestyStrip.tsx
new file mode 100644
index 0000000..bb4cf20
--- /dev/null
+++ b/src/components/discovery/HonestyStrip.tsx
@@ -0,0 +1,38 @@
+/**
+ * 정직성 인용문 + 3필 (하지 않을 것).
+ * 순위·노출 보장형 문구 금지, "언급률과 신뢰구간으로 보고" 프레임 유지.
+ */
+import { motion } from 'motion/react';
+
+const PILLS = [
+ '효과 없는 llms.txt는 팔지 않습니다',
+ '순위·노출을 보장하지 않습니다',
+ '단발 측정으로 보고하지 않습니다',
+];
+
+export default function HonestyStrip() {
+ return (
+
+
+
+ "스키마를 넣으면 인용된다고 약속하지 않습니다.
+
인용될 수 없는 이유를 전부 제거하고, 결과를 반복
+ 측정으로 증명합니다."
+
+
+ {PILLS.map((pill) => (
+
+ {pill}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/discovery/HowItWorks.tsx b/src/components/discovery/HowItWorks.tsx
new file mode 100644
index 0000000..9076757
--- /dev/null
+++ b/src/components/discovery/HowItWorks.tsx
@@ -0,0 +1,103 @@
+/**
+ * How It Works — Audit · Build · Prove 3단계 glass-card.
+ * 아이콘은 시안의 커스텀 SVG를 그대로 사용 (F3F0FF 스퀘어 + accent 스트로크).
+ */
+import type { ReactNode } from 'react';
+import { motion } from 'motion/react';
+
+interface Step {
+ step: string;
+ title: string;
+ detail: string;
+ icon: ReactNode;
+}
+
+interface HowItWorksProps {
+ criteriaCount: number;
+ categoryCount: number;
+}
+
+export default function HowItWorks({ criteriaCount, categoryCount }: HowItWorksProps) {
+ const steps: Step[] = [
+ {
+ step: 'STEP 01',
+ title: 'Audit',
+ detail: `AEO/GEO 기준표 v1.0으로 ${criteriaCount}개 항목을 실측합니다. 크롤러 접근성부터 인용 가능 콘텐츠, 측정 인프라까지 ${categoryCount}개 카테고리 100점 척도. 항목마다 근거 등급을 명시합니다.`,
+ icon: (
+
+ ),
+ },
+ {
+ step: 'STEP 02',
+ title: 'Build',
+ detail:
+ 'MedicalClinic·Physician 스키마를 심고, 흩어진 표면의 NAP을 한 글자까지 통일합니다. 핵심 페이지에 정의문과 시술별 FAQ를 넣어 AI가 잘라 갈 단락을 만듭니다.',
+ icon: (
+
+ ),
+ },
+ {
+ step: 'STEP 03',
+ title: 'Prove',
+ detail:
+ 'GA4 AI 채널그룹과 서버로그 크롤러 검증을 설치하고, 질의 10개 × 5회 반복 스팟체크로 변화를 측정합니다. 단발 조회가 아니라 언급률과 신뢰구간으로만 보고합니다.',
+ icon: (
+
+ ),
+ },
+ ];
+
+ return (
+
+
+
+ HOW IT WORKS
+
+ 진단하고, 구축하고, 숫자로 증명합니다.
+
+
+
+
+ {steps.map((item, index) => (
+
+
+ {item.icon}
+
+
+ {item.step}
+ {item.title}
+
+ {item.detail}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/discovery/Roadmap.tsx b/src/components/discovery/Roadmap.tsx
new file mode 100644
index 0000000..7f0ed18
--- /dev/null
+++ b/src/components/discovery/Roadmap.tsx
@@ -0,0 +1,77 @@
+/**
+ * 8주 로드맵 타임라인. 타이틀의 등급 전환(C → B)은 계산값에서 파생.
+ */
+import { motion } from 'motion/react';
+import type { OverallScore } from '../../types/discovery';
+
+interface RoadmapProps {
+ grade: OverallScore['grade'];
+ targetGrade: OverallScore['grade'] | null;
+ criteriaCount: number;
+ unverifiedCount: number;
+}
+
+export default function Roadmap({ grade, targetGrade, criteriaCount, unverifiedCount }: RoadmapProps) {
+ const phases = [
+ {
+ week: 'W1–2',
+ title: '기반 설치',
+ detail: '스키마·NAP·측정 인프라를 설치하고 기준선 스팟체크를 1회 측정합니다.',
+ },
+ {
+ week: 'W3–6',
+ title: '콘텐츠 구조',
+ detail: '정의문·FAQ·H1 재설계와 HTML 비교표를 구축하고 WordPress를 업그레이드합니다.',
+ },
+ {
+ week: 'W7–8',
+ title: '재진단·보고',
+ detail: `${criteriaCount}개 항목을 재측정하고 미검증 ${unverifiedCount}개를 반영해 총점을 다시 산정합니다.`,
+ },
+ ];
+
+ return (
+
+
+
+ 8-WEEK ROADMAP
+
+ {targetGrade
+ ? `8주 안에, ${grade}에서 ${targetGrade}로 올립니다.`
+ : `8주마다, ${grade} 등급을 재검증합니다.`}
+
+
+
+
+ {/* Connecting line (desktop only) */}
+
+
+ {phases.map((phase, index) => (
+
+
+ {phase.week}
+ {phase.title}
+
+ {phase.detail}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/components/discovery/SampleReport.tsx b/src/components/discovery/SampleReport.tsx
new file mode 100644
index 0000000..631d531
--- /dev/null
+++ b/src/components/discovery/SampleReport.tsx
@@ -0,0 +1,158 @@
+/**
+ * 샘플 리포트 섹션 — 뷰성형외과 실측 스냅샷.
+ * 점수·등급·카테고리 %·항목 수는 전부 discoveryScore 계산값에서 렌더 (하드코딩 금지).
+ * 발견 칩 3장의 카피는 result.keyFindings를 랜딩용으로 압축한 시안 확정 문안이다.
+ */
+import { motion } from 'motion/react';
+import type { DiscoveryResult, OverallScore } from '../../types/discovery';
+import { scorePctColor } from '../../lib/discoveryScore';
+import { ScoreRing } from '../report/ui/ScoreRing';
+
+const GRADE_BADGE_STYLE: Record = {
+ A: 'bg-[#EFF0FF] border-[#C5CBF5] text-[#3A3F7C]',
+ B: 'bg-[#F3F0FF] border-[#D5CDF5] text-[#4A3A7C]',
+ C: 'bg-[#FFF6ED] border-[#F5E0C5] text-[#7C5C3A]',
+ D: 'bg-[#FFF0F0] border-[#F5D5DC] text-[#7C3A4B]',
+};
+
+/** keyFindings 압축본 (시안 확정 카피). 수치는 2026-08-28 실측 스냅샷과 일치한다. */
+const FINDING_CHIPS = [
+ {
+ label: '치명',
+ chipClass: 'bg-[#FFF0F0] border-[#F5D5DC] text-[#7C3A4B]',
+ dotColor: '#D4889A',
+ title: 'AI가 누구인지 확정 못 함',
+ detail: 'MedicalClinic 스키마 없음. 시술 목록 JSON-LD는 문법 오류로 통째로 버려집니다.',
+ },
+ {
+ label: '보완',
+ chipClass: 'bg-[#FFF6ED] border-[#F5E0C5] text-[#7C5C3A]',
+ dotColor: '#D4A872',
+ title: '인용할 문장이 없음',
+ detail: '정의문 0개, FAQ 0개, HTML 표 0개. 답변엔진이 잘라 갈 단락이 없습니다.',
+ },
+ {
+ label: '강점',
+ chipClass: 'bg-[#F3F0FF] border-[#D5CDF5] text-[#4A3A7C]',
+ dotColor: '#9B8AD4',
+ title: '표면 자산은 이미 강함',
+ detail: '강남언니 후기 19,316건, GBP 4.9점. 홈페이지가 따라가면 바로 인용 근거가 됩니다.',
+ },
+];
+
+interface SampleReportProps {
+ overall: OverallScore;
+ result: DiscoveryResult;
+}
+
+export default function SampleReport({ overall, result }: SampleReportProps) {
+ const totalCriteria = overall.categories.reduce((sum, c) => sum + c.totalCount, 0);
+ const verifiedCriteria = overall.categories.reduce((sum, c) => sum + c.verifiedCount, 0);
+ const unverifiedCriteria = totalCriteria - verifiedCriteria;
+
+ return (
+
+
+
+
+ SAMPLE REPORT · {result.clinicName} 실측
+
+
+ 읽을 수는 있지만, 인용할 근거가 없습니다.
+
+
+ 실제 1차 진단 결과입니다. 어디가 강하고 어디가 비어 있는지, 점수는 카테고리 단위로
+ 드러납니다.
+
+
+
+
+ {/* Score ring card */}
+
+
+
+
+ 등급 {overall.grade}
+
+
AI Answer Readiness
+
+
+ {totalCriteria}개 항목 중 {verifiedCriteria}개 실측.
+
+ 미검증 {unverifiedCriteria}개는 분모에서 제외했습니다.
+
+
+
+ {/* Category bars */}
+
+ {overall.categories.map((cat) => {
+ const color = scorePctColor(cat.pct);
+ return (
+
+
+
+ {cat.category.code} · {cat.category.name}
+
+
+ {cat.pct}%
+
+
+
+
+
+
+ );
+ })}
+
+
+
+ {/* Finding chips */}
+
+ {FINDING_CHIPS.map((finding, index) => (
+
+
+
+ {finding.label}
+
+ {finding.title}
+ {finding.detail}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/discovery/WhyNow.tsx b/src/components/discovery/WhyNow.tsx
new file mode 100644
index 0000000..129298f
--- /dev/null
+++ b/src/components/discovery/WhyNow.tsx
@@ -0,0 +1,70 @@
+/**
+ * Why Now 다크 밴드.
+ * ⚠️ 3개 수치(12종 크롤러 / 네이버 기여 0건 / Direct 유실 35~70%)는 근거 있는 실측·문헌 값.
+ * 새 시장 통계를 임의로 추가하지 않는다 (근거 확인 후에만).
+ */
+import { motion } from 'motion/react';
+
+const STATS = [
+ {
+ figure: '12종',
+ title: 'AI 크롤러가 이미 읽는 중',
+ detail:
+ 'GPTBot·OAI-SearchBot·PerplexityBot 등이 오늘도 병원 사이트를 수집합니다. 문제는 접근이 아니라 인용할 내용입니다.',
+ },
+ {
+ figure: '0건',
+ title: '네이버 자산의 AI 기여',
+ detail:
+ '네이버 블로그·플레이스는 robots.txt로 AI 크롤러를 전면 차단합니다(2026-08 실측). 지금까지의 네이버 마케팅은 AI 답변에 닿지 않습니다.',
+ },
+ {
+ figure: '35~70%',
+ title: 'Direct로 숨는 AI 유입',
+ detail:
+ 'AI가 보낸 방문의 상당수는 유입 경로 없이 도착합니다. 측정 인프라 없이는 이미 오는 환자도 세지 못합니다.',
+ },
+];
+
+export default function WhyNow() {
+ return (
+
+
+
+ WHY NOW
+
+ 검색은 끝났고, 답변이 시작됐습니다.
+
+
+ 답변엔진은 광고 입찰이 아니라 크롤링 가능한 오픈웹 근거로 병원을 고릅니다. 인용되지
+ 못하면 비교 대상에도 오르지 못합니다.
+
+
+
+
+ {STATS.map((stat, index) => (
+
+
+ {stat.figure}
+
+ {stat.title}
+ {stat.detail}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/discovery/discoveryContact.ts b/src/components/discovery/discoveryContact.ts
new file mode 100644
index 0000000..d9cb4be
--- /dev/null
+++ b/src/components/discovery/discoveryContact.ts
@@ -0,0 +1,8 @@
+/**
+ * AI Discovery 랜딩 전용 연락처.
+ * 랜딩 CTA는 o2oteam@o2o.kr 단일 채널 (전역 Navbar의 contact@o2o.kr과 별도 트랙).
+ */
+export const DISCOVERY_CONTACT_EMAIL = 'o2oteam@o2o.kr';
+
+export const buildDiscoveryMailto = (subject: string) =>
+ `mailto:${DISCOVERY_CONTACT_EMAIL}?subject=${encodeURIComponent(`[INFINITH AI Discovery] ${subject}`)}`;
diff --git a/src/components/report/ui/ScoreRing.tsx b/src/components/report/ui/ScoreRing.tsx
index bc392e9..9bbec5f 100644
--- a/src/components/report/ui/ScoreRing.tsx
+++ b/src/components/report/ui/ScoreRing.tsx
@@ -6,6 +6,10 @@ interface ScoreRingProps {
size?: number;
label?: string;
color?: string;
+ /** 링 중앙 숫자 타이포 오버라이드 (기본 text-2xl) */
+ valueClassName?: string;
+ /** 링 중앙 숫자 아래 보조 표기 (예: "/ 100") */
+ subLabel?: string;
}
function getScoreColor(score: number, maxScore: number): string {
@@ -22,6 +26,8 @@ export function ScoreRing({
size = 120,
label,
color,
+ valueClassName = 'text-2xl',
+ subLabel,
}: ScoreRingProps) {
const strokeWidth = 8;
const radius = (size - strokeWidth) / 2;
@@ -61,8 +67,9 @@ export function ScoreRing({
transition={{ duration: 1, ease: 'easeOut' }}
/>
-
-
{score}
+
+ {score}
+ {subLabel && {subLabel}}
{label && (
diff --git a/src/lib/discoveryScore.ts b/src/lib/discoveryScore.ts
index b4ee99f..efd1886 100644
--- a/src/lib/discoveryScore.ts
+++ b/src/lib/discoveryScore.ts
@@ -30,6 +30,23 @@ export const gradeFromScore = (score: number): OverallScore['grade'] => {
return 'D';
};
+/** 점수 구간별 색 (ScoreRing과 동일 기준) */
+export const scorePctColor = (pct: number) =>
+ pct <= 40 ? '#C084CF' : pct <= 60 ? '#8B9CF7' : pct <= 80 ? '#7C6DD8' : '#6C5CE7';
+
+/**
+ * 다음 등급까지의 목표 점수. gradeFromScore의 경계와 반드시 일치해야 한다.
+ * 재진단 목표 표기에 사용 (예: 48/C → { score: 61, grade: 'B' }). A등급이면 null.
+ */
+export const nextGradeTarget = (
+ score: number,
+): { score: number; grade: OverallScore['grade'] } | null => {
+ if (score <= 40) return { score: 41, grade: 'C' };
+ if (score <= 60) return { score: 61, grade: 'B' };
+ if (score <= 80) return { score: 81, grade: 'A' };
+ return null;
+};
+
export function scoreDiscovery(rubric: Rubric, result: DiscoveryResult): OverallScore {
const byId = new Map(result.results.map((r) => [r.criterionId, r]));
diff --git a/src/main.tsx b/src/main.tsx
index 1c46b8c..ba528d5 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -15,6 +15,7 @@ import DataValidationPage from './pages/DataValidationPage.tsx';
import ClinicProfilePage from './pages/ClinicProfilePage.tsx';
import ApiDashboardPage from './pages/ApiDashboardPage.tsx';
import LoginPage from './pages/LoginPage.tsx';
+import DiscoveryLandingPage from './pages/DiscoveryLandingPage.tsx';
import DiscoveryReportPage from './pages/DiscoveryReportPage.tsx';
import './index.css';
@@ -37,6 +38,7 @@ createRoot(document.getElementById('root')!).render(
} />
} />
} />
+ } />
} />
diff --git a/src/pages/DiscoveryLandingPage.tsx b/src/pages/DiscoveryLandingPage.tsx
new file mode 100644
index 0000000..cf9bf89
--- /dev/null
+++ b/src/pages/DiscoveryLandingPage.tsx
@@ -0,0 +1,53 @@
+/**
+ * /discovery — AI Discovery(AEO/GEO) 서비스 랜딩.
+ * 시안: docs/reports/ai_discovery_landing.html (2026-08-31 확정)
+ *
+ * 데이터 원칙: 점수·등급·카테고리 %·항목 수는 전부
+ * scoreDiscovery(AEO_GEO_RUBRIC, DISCOVERY_VIEWCLINIC) 계산값에서 파생한다.
+ * 재진단으로 데이터가 바뀌면 랜딩 수치도 함께 바뀐다 (JSX 하드코딩 금지).
+ */
+import { useMemo } from 'react';
+import { AEO_GEO_RUBRIC } from '../data/aeoGeoRubric';
+import { DISCOVERY_VIEWCLINIC } from '../data/discovery_viewclinic';
+import { scoreDiscovery, nextGradeTarget } from '../lib/discoveryScore';
+import DiscoveryHero from '../components/discovery/DiscoveryHero';
+import WhyNow from '../components/discovery/WhyNow';
+import HowItWorks from '../components/discovery/HowItWorks';
+import SampleReport from '../components/discovery/SampleReport';
+import Roadmap from '../components/discovery/Roadmap';
+import HonestyStrip from '../components/discovery/HonestyStrip';
+import DiscoveryCta from '../components/discovery/DiscoveryCta';
+
+export default function DiscoveryLandingPage() {
+ const result = DISCOVERY_VIEWCLINIC;
+ const overall = useMemo(() => scoreDiscovery(AEO_GEO_RUBRIC, result), [result]);
+ const target = nextGradeTarget(overall.score);
+
+ const criteriaCount = AEO_GEO_RUBRIC.criteria.length;
+ const categoryCount = AEO_GEO_RUBRIC.categories.length;
+ const verifiedCount = overall.categories.reduce((sum, c) => sum + c.verifiedCount, 0);
+ const unverifiedCount = criteriaCount - verifiedCount;
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}