chore(supporters): 통합 뒤 템플릿 재생성 (주변 안내 포함, 영어 홈 안전 기본값)

정본 supporters/ 와 템플릿의 페이지 집합이 같아졌다.
/stay·/en/stay·/en·/plan·/en/plan 이 양쪽에 모두 있다.

검증
- 뷰 34페이지, 게이트 41/41, check.mjs 오류 0건.
- 빈 데이터 템플릿 16페이지, 영어 홈에 병원 고유 사실 0건, 문장 성립.
- 내보내기 종료코드 0, tsc 0 에러.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Haewon Kam 2026-09-12 15:21:31 +09:00
parent 0a0f64c29e
commit 82aa56fa40
5 changed files with 189 additions and 3 deletions

View File

@ -24,5 +24,9 @@
"sponsorNotice": "",
"ga4MeasurementId": "",
"_comment_ga4": "GA4 측정 ID(G-XXXX). 비우면 태그를 넣지 않는다. 키 이벤트 이름은 data/ai_channels.json conversion_events 와 같다.",
"heroImageAltEn": ""
"heroImageAltEn": "",
"indexNowKey": "",
"googleSiteVerification": "",
"naverSiteVerification": "",
"_comment_publish": "indexNowKey 는 워커 data 단계가 자동 생성(public/<key>.txt). googleSiteVerification·naverSiteVerification 은 각 콘솔의 HTML 태그 content 값을 사람이 넣는다(briefs/<clinic>/clinic.json site 로 전달 가능). 발행 단계는 scripts/publish_site.mjs."
}

View File

@ -75,6 +75,7 @@ document.addEventListener('click',function(e){var a=e.target.closest&&e.target.c
<a href="/posts">{u.nav_posts}</a>
<a href="/videos">{u.nav_videos}</a>
<a href="/newsroom">{u.nav_news}</a>
<a href="/stay">{u.nav_stay}</a>
<a href="/visit">{u.nav_visit}</a>
<a href="/plan">{u.nav_plan}</a>
<a href="/about">{u.nav_about}</a>
@ -82,6 +83,7 @@ document.addEventListener('click',function(e){var a=e.target.closest&&e.target.c
) : (
<>
<a href="/en">{u.nav_home}</a>
<a href="/en/stay">{u.nav_stay}</a>
<a href="/en/plan">{u.nav_plan}</a>
<a href="/en#visit">{u.nav_visit}</a>
<a href="/en#clinic">{u.nav_clinic}</a>

View File

@ -6,6 +6,7 @@ export type Lang = 'ko' | 'en';
const PAIRS: [string, string][] = [
['/', '/en'],
['/plan', '/en/plan'],
['/stay', '/en/stay'],
];
export function switchHref(pathname: string, to: Lang): string {
const p = pathname.replace(/\/index\.html$/, '/').replace(/\.html$/, '').replace(/\/$/, '') || '/';
@ -21,7 +22,7 @@ export const UI: Record<Lang, Record<string, string>> = {
ko: {
sample: '샘플 사이트입니다. 정식 공개 전입니다.',
brand: '서포터즈',
nav_posts: '상담 전 질문', nav_videos: '의료진 영상', nav_news: '뉴스룸', nav_visit: '방문 안내', nav_plan: '회복 일정', nav_about: '매체 소개',
nav_posts: '상담 전 질문', nav_videos: '의료진 영상', nav_news: '뉴스룸', nav_stay: '주변 안내', nav_visit: '방문 안내', nav_plan: '회복 일정', nav_about: '매체 소개',
cta: '상담 예약',
back: '이전 페이지', backAria: '이전 페이지로 돌아가기', totop: '맨 위로',
switchLabel: 'EN', switchAria: 'Switch to English',
@ -33,7 +34,7 @@ export const UI: Record<Lang, Record<string, string>> = {
en: {
sample: 'Sample site. Not yet public.',
brand: 'Supporters',
nav_home: 'Home', nav_plan: 'Recovery Planner', nav_visit: 'Getting Here', nav_clinic: 'Clinic Facts', nav_korean: '한국어 전체 보기',
nav_home: 'Home', nav_stay: 'Nearby', nav_plan: 'Recovery Planner', nav_visit: 'Getting Here', nav_clinic: 'Clinic Facts', nav_korean: '한국어 전체 보기',
cta: 'Book a consultation',
back: 'Back', backAria: 'Go back to the previous page', totop: 'Back to top',
switchLabel: 'KO', switchAria: '한국어로 보기',

View File

@ -12,6 +12,8 @@ export const GET: APIRoute = async ({ site }) => {
{ loc: `${base}/clinic`, lastmod: today },
{ loc: `${base}/visit`, lastmod: today },
{ loc: `${base}/en`, lastmod: today },
{ loc: `${base}/stay`, lastmod: today },
{ loc: `${base}/en/stay`, lastmod: today },
{ loc: `${base}/plan`, lastmod: today },
{ loc: `${base}/en/plan`, lastmod: today },
{ loc: `${base}/newsroom`, lastmod: today },

View File

@ -280,3 +280,180 @@ td { color: var(--slate-700); }
.site-header .nav a { margin-left: 0; flex: none; font-size: 0.9rem; }
}
/* ── /en/stay ── */
.stay { padding-bottom: 4rem; }
.stay .lede { color: var(--slate-600, #475569); margin-top: 0.6rem; }
.stay section { margin-top: 3.4rem; }
.stay .sec-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.stay .sec-head h2 { margin: 0; }
.stay .upd { font-size: 0.8rem; color: var(--slate-500, #64748B); }
.stay .sub { color: var(--slate-600, #475569); margin: 0.4rem 0 1rem; }
.stay .src { font-size: 0.84rem; color: var(--slate-500, #64748B); margin-top: 1rem; }
.stay .chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.2rem 0 1.6rem; }
.stay .chip {
border: 1px solid var(--line, #E2E8F0); background: #fff; border-radius: 999px;
padding: 0.36rem 0.85rem; font: inherit; font-size: 0.86rem; color: var(--slate-600, #475569);
cursor: pointer; transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.stay .chip b { font-weight: 700; opacity: 0.55; margin-left: 0.25em; }
.stay .chip:hover { border-color: var(--primary-900, #0A1128); }
.stay .chip.on { background: var(--primary-900, #0A1128); border-color: var(--primary-900, #0A1128); color: #fff; }
.stay .chip.on b { opacity: 0.7; }
.stay .grp { margin-bottom: 2.2rem; }
.stay .grp h3 { margin: 0 0 0.9rem; }
.stay .grp h3 small { font-weight: 400; font-size: 0.82rem; color: var(--slate-500, #64748B); margin-left: 0.45em; }
.stay .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(248px, 1fr)); gap: 1rem; }
.stay .card {
background: #fff; border: 1px solid var(--line, #E2E8F0); border-radius: 14px;
overflow: hidden; display: flex; flex-direction: column;
box-shadow: 3px 4px 12px rgba(0, 0, 0, 0.05);
}
.stay .card .thumb { position: relative; height: 148px; background: #EEF1F6; display: flex; align-items: center; justify-content: center; }
.stay .card .thumb img { width: 100%; height: 148px; object-fit: cover; display: block; }
.stay .card .nophoto {
font-size: 1.02rem; font-weight: 700; color: var(--slate-500, #64748B);
padding: 0 1rem; text-align: center; line-height: 1.35; word-break: keep-all;
}
.stay .card .badge {
position: absolute; left: 0.6rem; bottom: 0.6rem;
background: rgba(10, 17, 40, 0.82); color: #fff; border-radius: 6px;
padding: 0.18rem 0.45rem; font-size: 0.74rem; font-weight: 600;
}
.stay .card .badge em { font-style: normal; opacity: 0.7; margin-left: 0.25em; }
.stay .card .badge.mono { letter-spacing: 0.06em; }
.stay .card .body { padding: 0.85rem 0.95rem 1rem; display: flex; flex-direction: column; flex: 1; }
.stay .card h4 { margin: 0 0 0.35rem; font-size: 0.98rem; }
.stay .card .when { font-size: 0.8rem; color: var(--slate-600, #475569); margin: 0 0 0.4rem; }
.stay .card .desc { font-size: 0.86rem; color: var(--slate-600, #475569); margin: 0 0 0.5rem; }
.stay .card .addr { font-size: 0.78rem; color: var(--slate-500, #64748B); margin: 0 0 0.6rem; }
.stay .card .go { font-size: 0.82rem; margin-top: auto; }
.stay .route { border: 1px solid var(--line, #E2E8F0); border-radius: 16px; padding: 1.3rem 1.4rem; margin-bottom: 1.4rem; background: #fff; }
.stay .route-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; flex-wrap: wrap; }
.stay .route-head h3 { margin: 0; }
.stay .route .who { font-size: 0.84rem; color: var(--slate-500, #64748B); margin: 0.15rem 0 0; }
.stay .route .clock { font-size: 0.86rem; font-weight: 600; color: var(--primary-900, #0A1128); white-space: nowrap; }
.stay .route .blurb { color: var(--slate-600, #475569); margin: 0.7rem 0 1rem; }
.stay .route .map { width: 100%; height: auto; display: block; border-radius: 12px; }
.stay .route .maphint { font-size: 0.76rem; color: var(--slate-500, #64748B); margin: 0.4rem 0 1rem; }
.stay .timeline { list-style: none; padding: 0; margin: 0; }
.stay .timeline .move { font-size: 0.8rem; color: var(--slate-500, #64748B); margin: 0.35rem 0 0.35rem 1.05rem; }
.stay .timeline .stop { display: flex; gap: 0.8rem; align-items: flex-start; }
.stay .timeline .n {
flex: none; width: 26px; height: 26px; border-radius: 50%;
background: var(--primary-900, #0A1128); color: #fff;
font-size: 0.8rem; font-weight: 700; display: grid; place-items: center; margin-top: 0.1rem;
}
.stay .timeline .stop-t { font-size: 0.8rem; color: var(--slate-500, #64748B); }
.stay .timeline h4 { margin: 0.1rem 0 0.2rem; font-size: 0.98rem; }
.stay .timeline p { margin: 0; font-size: 0.86rem; color: var(--slate-600, #475569); }
.stay .botnote { margin-top: 1.6rem; }
/* 시술 후 관리 안내(병원 원문 인용). 동선 카드(.route)·정차(.stop) 룩을 그대로 쓰고 접이식 동작만 더한다. 새 색·크기 없음. */
.stay details.recovery > summary.btn { list-style: none; cursor: pointer; }
.stay details.recovery > summary.btn::-webkit-details-marker { display: none; }
.stay details.recovery > summary.btn .hide { display: none; }
.stay details.recovery[open] > summary.btn .show { display: none; }
.stay details.recovery[open] > summary.btn .hide { display: inline; }
.stay details.recovery[open] > summary.btn { margin-bottom: 1.1rem; }
.stay details.recovery > .timeline { margin-top: 0.2rem; }
.stay .recovery .timeline > li { border-top: 1px solid var(--line, #E2E8F0); padding: 0.7rem 0; }
.stay .recovery .timeline > li:first-child { border-top: 0; padding-top: 0; }
.stay .recovery summary { cursor: pointer; list-style: none; }
.stay .recovery summary::-webkit-details-marker { display: none; }
.stay .recovery summary .stop { display: flex; }
.stay .recovery summary h4 { margin: 0.1rem 0 0; }
.stay .recovery details[open] summary h4 { text-decoration: underline; text-underline-offset: 0.2em; text-decoration-color: var(--slate-200, #E2E8F0); }
.stay .recovery details > ul { margin: 0.7rem 0 0.5rem 2.2rem; padding-left: 1.1rem; }
.stay .recovery details > ul li { font-size: 0.9rem; color: var(--slate-700, #334155); margin-bottom: 0.35rem; }
.stay .recovery details > p { margin: 0 0 0 2.2rem; font-size: 0.84rem; color: var(--slate-500, #64748B); }
@media (max-width: 640px) { .cards { grid-template-columns: 1fr; } }
/* 섹션 점프. 머리말 아래에 붙어 스크롤을 따라온다. 헤더(68px) 아래에 걸리게 top 을 맞춘다. */
.stay .jump {
position: sticky; top: 68px; z-index: 9;
display: flex; flex-wrap: wrap; gap: 0.45rem;
margin: 1.4rem 0 2.4rem;
padding: 0.62rem 0.68rem;
background: rgba(255, 255, 255, 0.58);
backdrop-filter: blur(16px) saturate(1.5);
-webkit-backdrop-filter: blur(16px) saturate(1.5);
border: 1px solid rgba(255, 255, 255, 0.6);
border-radius: 18px;
box-shadow: 0 8px 28px rgba(10, 17, 40, 0.09), inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.stay .jump a {
display: inline-flex; align-items: center; gap: 0.42rem;
padding: 0.46rem 0.9rem;
border-radius: 999px;
font-size: 0.86rem; font-weight: 600; letter-spacing: -0.005em;
color: var(--primary-900, #0A1128);
text-decoration: none; white-space: nowrap;
background: #fff;
border: 1px solid var(--line, #E2E8F0);
box-shadow: 0 1px 2px rgba(10, 17, 40, 0.06);
transition: transform 0.14s ease, box-shadow 0.14s ease, background 0.14s, border-color 0.14s, color 0.14s;
}
.stay .jump a:hover {
text-decoration: none;
transform: translateY(-1px);
border-color: #C5CBF5;
box-shadow: 0 4px 12px rgba(10, 17, 40, 0.12);
}
.stay .jump a:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(10, 17, 40, 0.08); }
.stay .jump a b {
font-weight: 700; font-size: 0.72rem; line-height: 1;
padding: 0.2rem 0.4rem; border-radius: 999px;
background: #EFF0FF; color: #3A3F7C;
}
/* 지금 보고 있는 섹션 */
.stay .jump a.on {
background: linear-gradient(to right, #4F1DA1, #021341);
border-color: transparent; color: #fff;
box-shadow: 0 4px 14px rgba(79, 29, 161, 0.32);
}
.stay .jump a.on b { background: rgba(255, 255, 255, 0.22); color: #fff; }
/* 점프로 이동했을 때 제목이 스티키 바에 가리지 않게 */
.stay section, .stay .grp { scroll-margin-top: 152px; }
@media (max-width: 640px) {
.stay .jump { top: 60px; gap: 0.35rem; padding: 0.5rem; border-radius: 14px; }
.stay .jump a { padding: 0.4rem 0.72rem; font-size: 0.8rem; }
}
/* 필터 칩도 눌리는 것처럼 보이게 */
.stay .chip {
box-shadow: 0 1px 2px rgba(10, 17, 40, 0.05);
transition: transform 0.14s ease, box-shadow 0.14s ease, background 0.15s, color 0.15s, border-color 0.15s;
}
.stay .chip:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(10, 17, 40, 0.1); }
.stay .chip:active { transform: translateY(0); }
.stay .chip.on { box-shadow: 0 4px 14px rgba(10, 17, 40, 0.22); }
/* 오늘 날씨. 값을 못 받으면 hidden 이라 아예 그려지지 않는다. */
.stay .wx {
display: flex; align-items: center; justify-content: space-between;
gap: 1.2rem; flex-wrap: wrap;
margin-top: 1.6rem; padding: 1rem 1.2rem;
background: linear-gradient(to right, #fff3eb, #e4cfff, #f5f9ff);
border-radius: 16px;
}
.stay .wx-now { display: flex; align-items: center; gap: 0.9rem; }
.stay .wx-temp { font-family: 'Playfair Display', serif; font-size: 2.5rem; font-weight: 700; line-height: 1; color: var(--primary-900, #0A1128); }
.stay .wx-unit { font-size: 1.1rem; vertical-align: 0.9rem; margin-left: 0.06em; opacity: 0.6; }
.stay .wx-cond { font-weight: 700; color: var(--primary-900, #0A1128); }
.stay .wx-meta { font-size: 0.8rem; color: var(--slate-600, #475569); margin-top: 0.15rem; }
.stay .wx-note { margin: 0; font-size: 0.88rem; color: var(--slate-700, #334155); max-width: 30rem; }
/* 동선 지도. OSM 타일을 좌표로 깔고 그 위에 핀을 얹는다. 출처 표기는 OSM 정책상 필수다. */
.stay .route .map {
position: relative; width: 100%; max-width: 680px; height: 340px;
border-radius: 12px; overflow: hidden; background: #E8EDF3;
border: 1px solid var(--line, #E2E8F0);
}
.stay .route .map-tiles { position: absolute; inset: 0; }
.stay .route .map-tiles .tile { position: absolute; width: 256px; height: 256px; background-size: 256px 256px; background-repeat: no-repeat; }
.stay .route .map-pins { position: absolute; inset: 0; width: 100%; height: 100%; }
.stay .route .map-credit {
position: absolute; right: 0; bottom: 0;
background: rgba(255, 255, 255, 0.82); color: var(--slate-600, #475569);
font-size: 0.66rem; padding: 0.1rem 0.35rem; border-top-left-radius: 6px;
}