"""/s/stay 를 세 페이지로 나눈 판을 /s/stay6 으로 굽는다. stay3·stay4 는 HTML5 UP 템플릿으로 새로 만든 것이라 렌더러와 무관했다. stay6 은 반대다 — **/s/stay 와 같은 리액트 렌더러·같은 payload** 를 쓰고, `theme.sections[].enabled` 만 페이지별로 갈라 SSR 을 세 번 돌린다. 그래서 캐러셀·미니 플레이어·엽서 캔버스·후기 모달이 전부 /s/stay 와 같은 것으로 선다. ★ 렌더러(solution/site/src)는 건드리지 않는다. 페이지마다 다른 것은 payload 의 enabled 와 주입하는 css·js 뿐이다. ★ 후기(ReviewSection)·엽서(PostcardMakerSection)는 HomePage 가 섹션 목록과 무관하게 항상 그린다. 페이지를 나누려면 렌더러를 고쳐야 하는데 그건 /s/stay 를 같이 바꾸는 일이라, 여기서는 주입 css 로 감춘다 — DOM 에는 남는다. ★ 히어로도 항상 나온다. 페이지 머리로 쓴다. 입력: build/index.html (patch_stay.py 결과 — 껍데기·head·주입분) · stay-payload-new.json 출력: build6/{index.html, gunsan/index.html, booking/index.html} """ import json import re import shutil import subprocess import sys import tempfile from pathlib import Path SP = Path(__file__).parent SITE = SP.parents[1] OUT = SP / "build6" SSR = SITE / "dist" / "ssr6" / "ssr6.js" SHELL = SP / "build" / "index.html" PAYLOAD = SP / "build" / "index.html" BASE = "/s/stay6" PAGES = [ ("", "소개", {"hero", "intro", "rooms", "video", "photos", "map", "weather"}, {"reviews"}), # ★ map 을 끄면 HomePage 가 '오시는 길' 을 기본으로 한 번 더 낸다(HomePage.tsx:118). # 군산 페이지에는 필요 없어서 주입 css 로 감춘다. ("gunsan", "군산", {"hero", "festival", "local", "itinerary", "story", "songs", "people", "chronicle", "reading"}, {"reviews", "postcard-maker", "location", "hero"}), ("booking", "예약·후기", {"hero", "info", "booking", "faq", "rules", "map"}, {"postcard-maker", "hero"}), ] # 화면 앵커 id → 그 섹션이 사는 페이지. # ★ 페이지를 나누면 헤더 nav 의 `#festival` 같은 앵커가 **그 페이지에 없는 자리**를 가리킨다. # 바를 하나 더 얹지 않고, 사이트가 이미 가진 nav 의 href 만 페이지 주소로 고친다. ANCHOR_PAGE = { "top": "", "about": "", "units": "", "video": "", "gallery": "", "location": "", "weather": "", "postcard-maker": "", "festival": "gunsan", "guide": "gunsan", "itinerary": "gunsan", "story": "gunsan", "songs": "gunsan", "info": "booking", "booking": "booking", "faq": "booking", "reviews": "booking", } # 하위 페이지의 머리. # ★ 실물 대조(2026-09-18, 星のや京都 /dining/ · 모바일 390×844): 하위 페이지에는 히어로가 # 없다. 얇은 헤더 → 여백 → **페이지 이름 한 줄(가운데·명조)** → 바로 내용이다. # 사진도 캐치프레이즈도 CTA 도 없다. 홈 히어로를 하위 페이지가 되풀이하는 판은 없었다. NAV_CSS = """ [data-w4d-here] { font-weight: 800; text-decoration: underline; text-underline-offset: 5px; } .w4d-pagehead { padding: clamp(2.5rem, 9vw, 4.5rem) 1.25rem clamp(1.75rem, 6vw, 3rem); text-align: center; } .w4d-pagehead h1 { margin: 0; font-family: var(--font-serif, serif); font-weight: 400; font-size: clamp(1.75rem, 7vw, 2.4rem); letter-spacing: .04em; line-height: 1.3; } """ # ★ HTML 을 고쳐도 소용없다 — 리액트가 하이드레이션하면서 헤더를 다시 그려 덮는다(실측). # 그래서 런타임에 고친다. 다시 그려도 되도록 클릭을 가로채는 쪽이 본체이고, # href 를 바꾸는 것은 마우스 올렸을 때 주소가 맞게 보이라고 덤으로 한다. NAV_JS = """ """ # 하위 페이지는 히어로를 쓰지 않는다(위 실물 대조). 렌더러가 무조건 그리므로 여기서 감춘다. HIDE_CSS = { "hero": "#top { display: none !important; }", "reviews": "#reviews { display: none !important; }", "postcard-maker": "#postcard-maker { display: none !important; }", "location": "#location { display: none !important; }", } def shell_parts(): html = SHELL.read_text(encoding="utf-8") open_tag = '
' start = html.index(open_tag) + len(open_tag) end = html.index('", end) + len("") return html[:start], html[close:end], html[payload_end:], html def baked_payload(): """굽힌 /s/stay 에서 payload 를 그대로 떠 온다 — stay-payload-new.json 은 patch_stay 중간 스냅샷이라 booking 켜기·탭 비우기·사진 주소 교체가 아직 안 들어가 있다.""" html = PAYLOAD.read_text(encoding="utf-8") start = html.index("window.__SITE_PAYLOAD__=") + len("window.__SITE_PAYLOAD__=") end = html.index("", start) return json.loads(html[start:end].rstrip().rstrip(";")) def variant(payload, keep): """stay 가 켜 둔 섹션 중 이 페이지 몫만 남긴다. 꺼져 있던 것을 켜지 않는다 — '군산 이야기' 안의 탭(가요·인물·시간의 골목·읽기)이 따로 서 버린다.""" out = json.loads(json.dumps(payload)) for section in out["theme"]["sections"]: section["enabled"] = bool(section.get("enabled")) and section["id"] in keep return out PAGE_TITLE = {"": None, "gunsan": "군산", "booking": "이용안내 및 예약"} def nav_js(current): return NAV_JS % { "base": json.dumps(BASE), "here": json.dumps(current), "page": json.dumps(ANCHOR_PAGE, ensure_ascii=False), "title": json.dumps(PAGE_TITLE[current], ensure_ascii=False), } def main(): if not SSR.exists(): sys.exit(f"SSR 번들이 없다: {SSR}\n cd {SITE} && npx vite build --ssr scripts/mockup/ssr6.ts --outDir dist/ssr6") if not SHELL.exists(): sys.exit(f"껍데기가 없다: {SHELL} — 먼저 patch_stay.py 를 돌린다") base_payload = baked_payload() head, tail_root, tail, whole = shell_parts() if OUT.exists(): shutil.rmtree(OUT) OUT.mkdir(parents=True) for slug, _label, keep, hide in PAGES: page_payload = variant(base_payload, keep) with tempfile.TemporaryDirectory() as tmp: src = Path(tmp) / "p.json" dst = Path(tmp) / "p.html" src.write_text(json.dumps(page_payload, ensure_ascii=False), encoding="utf-8") subprocess.run(["node", str(SSR), str(src), str(dst)], check=True) app = dst.read_text(encoding="utf-8") payload_js = f"" html = head + app + tail_root + payload_js + tail html = html.replace("", nav_js(slug) + "", 1) extra = NAV_CSS + "".join(HIDE_CSS[key] for key in sorted(hide)) html = html.replace("", f"", 1) html = html.replace("/s/stay/", f"{BASE}/") html = re.sub(r'(