o2o-site-AEO/backend/demo_site.html
Mina Choi 6784e59ca5 최초 커밋 — 기존 코드 전체 + 문서 체계 신설
git 저장소가 없어 히스토리·협업 기반이 아예 없던 상태를 연다.
함께 문서를 재편했다. 그동안 문서가 있어도 "이 제품이 뭘 푸는가"와
"어떻게 도는가"를 담은 문서가 없어서, 목표 문장이 backend/frontend
README 두 곳에 복붙돼 있었다 — 상위 문서가 없어 아래로 샌 것이다.

신설
  README.md               레포 진입점 + 문서 지도 + 문서 규칙 4가지
  AGENTS.md               에이전트·신규 합류자용 함정 목록과 규약
                          (CLAUDE.md 는 여기로 걸린 심볼릭 링크)
  docs/PRODUCT.md         제품 정의 — 문제·사용자·원칙·**non-goals**·성공 기준
  docs/ARCHITECTURE.md    payload 경계·발행 파이프라인·서빙 결정·앱 분리 설계

이동
  backend/docs/DECISIONS.md → docs/DECISIONS.md
    백엔드만의 결정이 아니다. 게다가 코드 주석 ~25곳이 이미
    `docs/DECISIONS.md` 로 적고 있어 레포 루트 기준으로는 그게 맞다.

갱신
  docs/DEPLOY.md          서빙 결정 반영 — nginx 정적 서빙이 지금 경로(3절),
                          Azure 는 나중에 켤 때(4절)로 분리
  docs/ARCHITECTURE.md    사이트 = 한 장(2026-08-31) 구조 반영
  docs/COLLECTION_SEO_AEO_FLOW.md
                          robots.txt·sitemap.xml 은 오리진 루트에만 굽는다는 점 명시
  frontend/site/scripts/prerender.ts
                          헤더 주석의 렌더 보고서 경로가 실제(422줄)와 달라 수정

.gitignore
  ★ CLAUDE.md 를 더 이상 무시하지 않는다. 에이전트 지침은 팀과 모든
    에이전트가 공유하는 규약이라 커밋해야 한다 — 무시하면 클론한 사람이
    "배포 후 republish_all.py 필수" 같은 함정을 전달받지 못한다.
    개인용 오버라이드는 ~/.claude/CLAUDE.md 에 둔다.
2026-08-31 13:57:59 +09:00

248 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>핑크비치펜션 - 숙박</title>
<meta name="description" content="하조대 해변에서 도보 3분 거리에 있는 2개 동 규모의 펜션입니다. 모든 객실에서 바다가 보이며, 취사가 가능하고 야외 바비큐 공간을 갖추고 있습니다.">
<style>
:root{--fg:#1a1a1a;--muted:#666;--line:#e5e5e5;--bg:#fff}
*{box-sizing:border-box}
body{margin:0;padding:0 16px 48px;font:16px/1.7 system-ui,-apple-system,"Apple SD Gothic Neo","Malgun Gothic",sans-serif;color:var(--fg);background:var(--bg)}
main{max-width:760px;margin:0 auto}
header{padding:32px 0 16px;border-bottom:2px solid var(--fg)}
h1{margin:0 0 4px;font-size:1.75rem;line-height:1.3}
.category{margin:0;color:var(--muted);font-size:.9rem}
section{padding:24px 0;border-bottom:1px solid var(--line)}
section:last-child{border-bottom:0}
h2{margin:0 0 12px;font-size:1.1rem}
h3{margin:0 0 8px;font-size:1rem}
dl{display:grid;grid-template-columns:minmax(7rem,auto) 1fr;gap:6px 16px;margin:0}
dt{color:var(--muted)}
dd{margin:0}
article{padding:12px 0;border-top:1px dashed var(--line)}
article:first-of-type{border-top:0}
figure{margin:0}
.photos{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px}
img{width:100%;height:auto;display:block;border-radius:6px}
figcaption{margin-top:4px;color:var(--muted);font-size:.85rem}
.faq details{border-top:1px dashed var(--line);padding:10px 0}
.faq details:first-of-type{border-top:0}
.faq summary{cursor:pointer;font-weight:600;list-style:none;display:flex;justify-content:space-between;gap:12px}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:"−";color:var(--muted);flex:none}
.faq details:not([open]) summary::after{content:"+"}
.faq p{margin:8px 0 0;color:var(--muted)}
a{color:inherit}
/* 앵커 네비 — 긴 페이지에서 원하는 정보로 바로 간다 */
html{scroll-behavior:smooth}
nav.toc{position:sticky;top:0;z-index:5;display:flex;gap:4px;overflow-x:auto;
padding:10px 0;margin-bottom:-1px;background:var(--bg);border-bottom:1px solid var(--line);
-webkit-overflow-scrolling:touch;scrollbar-width:none}
nav.toc::-webkit-scrollbar{display:none}
nav.toc a{flex:none;padding:6px 12px;border:1px solid var(--line);border-radius:999px;
font-size:.85rem;text-decoration:none;color:var(--muted);white-space:nowrap}
nav.toc a:hover,nav.toc a:focus-visible{border-color:var(--fg);color:var(--fg)}
section{scroll-margin-top:56px}
/* 사진 — 클릭하면 크게. JS 가 없으면 그냥 그리드로 다 보인다 */
.photos button{all:unset;display:block;cursor:zoom-in;width:100%}
.photos button:focus-visible img{outline:2px solid var(--fg);outline-offset:2px}
.lightbox{position:fixed;inset:0;z-index:50;display:none;place-items:center;
background:rgba(0,0,0,.92);padding:16px}
.lightbox[open],.lightbox.on{display:grid}
.lightbox img{max-width:100%;max-height:78vh;width:auto;border-radius:4px}
.lightbox figcaption{margin-top:12px;color:#eee;text-align:center;font-size:.9rem}
.lightbox .nav{position:absolute;top:50%;transform:translateY(-50%);
background:rgba(255,255,255,.15);color:#fff;border:0;border-radius:999px;
width:44px;height:44px;font-size:1.4rem;cursor:pointer}
.lightbox .prev{left:12px}.lightbox .next{right:12px}
.lightbox .close{position:absolute;top:12px;right:12px;background:none;border:0;
color:#fff;font-size:1.8rem;line-height:1;cursor:pointer;padding:8px}
/* 하단 고정 연락 바 — 모바일에서 전화가 제일 중요한 행동이다 */
.cta{position:fixed;left:0;right:0;bottom:0;z-index:10;display:flex;gap:8px;
padding:10px 16px calc(10px + env(safe-area-inset-bottom));
background:var(--bg);border-top:1px solid var(--line)}
.cta a,.cta button{flex:1;padding:12px;border-radius:8px;border:1px solid var(--fg);
text-align:center;text-decoration:none;font-weight:600;font-size:.95rem;
background:var(--bg);color:var(--fg);cursor:pointer;font-family:inherit}
.cta a.primary{background:var(--fg);color:var(--bg)}
body:has(.cta){padding-bottom:96px}
@media(min-width:761px){.cta{position:static;max-width:760px;margin:24px auto 0;border:0;padding:0}
body:has(.cta){padding-bottom:48px}}
</style>
<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"LodgingBusiness","name":"핑크비치펜션","address":{"@type":"PostalAddress","streetAddress":"강원특별자치도 양양군 현북면 하조대3길 11","addressCountry":"KR"},"telephone":"033-672-0000","geo":{"@type":"GeoCoordinates","latitude":38.0219217,"longitude":128.7221449},"image":["https://picsum.photos/seed/pension-out/1200/800","https://picsum.photos/seed/pension-bed/1200/800","https://picsum.photos/seed/pension-liv/1200/800","https://picsum.photos/seed/pension-bbq/1200/800","https://picsum.photos/seed/pension-duo/1200/800"],"description":"하조대 해변에서 도보 3분 거리에 있는 2개 동 규모의 펜션입니다. 모든 객실에서 바다가 보이며, 취사가 가능하고 야외 바비큐 공간을 갖추고 있습니다.","checkinTime":"15:00","checkoutTime":"11:00","petsAllowed":false,"amenityFeature":[{"@type":"LocationFeatureSpecification","name":"와이파이","value":true},{"@type":"LocationFeatureSpecification","name":"주차 가능","value":true},{"@type":"LocationFeatureSpecification","name":"조식 제공","value":false},{"@type":"LocationFeatureSpecification","name":"바비큐 이용","value":true}],"makesOffer":[{"@type":"Offer","name":"A동 스탠다드","price":150000,"priceCurrency":"KRW"},{"@type":"Offer","name":"B동 복층","price":220000,"priceCurrency":"KRW"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"체크인·체크아웃은 몇 시인가요?","acceptedAnswer":{"@type":"Answer","text":"체크인은 15:00, 체크아웃은 11:00 입니다."}},{"@type":"Question","name":"반려동물과 함께 갈 수 있나요?","acceptedAnswer":{"@type":"Answer","text":"반려동물 동반은 불가합니다."}},{"@type":"Question","name":"객실에서 취사가 가능한가요?","acceptedAnswer":{"@type":"Answer","text":"취사가 가능하며 모든 객실에 주방이 있습니다."}},{"@type":"Question","name":"바비큐를 이용하려면 얼마인가요?","acceptedAnswer":{"@type":"Answer","text":"바비큐 이용료는 20,000원입니다."}},{"@type":"Question","name":"주차는 가능한가요?","acceptedAnswer":{"@type":"Answer","text":"주차가 가능하며 6대까지 주차할 수 있습니다."}}]}]}</script>
</head>
<body>
<main>
<nav class="toc"><a href="#intro">소개</a><a href="#info">이용 정보</a><a href="#units">객실</a><a href="#photos">사진</a><a href="#faq">자주 묻는 질문</a><a href="#contact">위치·연락처</a></nav>
<header>
<h1 data-ld="name">핑크비치펜션</h1>
<p class="category">숙박</p>
</header>
<section id="intro">
<p data-ld="description">하조대 해변에서 도보 3분 거리에 있는 2개 동 규모의 펜션입니다. 모든 객실에서 바다가 보이며, 취사가 가능하고 야외 바비큐 공간을 갖추고 있습니다.</p>
</section>
<section id="contact">
<h2>위치·연락처</h2>
<dl>
<dt>주소</dt><dd data-ld="streetAddress">강원특별자치도 양양군 현북면 하조대3길 11</dd>
<dt>전화</dt><dd><a data-ld="telephone" href="tel:0336720000">033-672-0000</a></dd>
</dl>
</section>
<section id="info">
<h2>이용 정보</h2>
<dl>
<dt>체크인 시간</dt><dd data-ld="checkinTime">15:00</dd>
<dt>체크아웃 시간</dt><dd data-ld="checkoutTime">11:00</dd>
<dt>취소·환불 규정</dt><dd>이용 7일 전 100% 환불, 3일 전 50% 환불, 당일 취소 불가</dd>
<dt>취사 가능</dt><dd>가능</dd>
<dt>반려동물 동반</dt><dd data-ld="petsAllowed">불가</dd>
<dt>흡연 가능</dt><dd>불가</dd>
<dt>프런트 운영시간</dt><dd>09:00 - 21:00</dd>
<dt>주차 가능</dt><dd data-ld-amenity="주차 가능">가능</dd>
<dt>주차 대수</dt><dd>6대</dd>
<dt>와이파이</dt><dd data-ld-amenity="와이파이">가능</dd>
<dt>바비큐 이용</dt><dd data-ld-amenity="바비큐 이용">가능</dd>
<dt>바비큐 이용료</dt><dd>20,000원</dd>
<dt>조식 제공</dt><dd data-ld-amenity="조식 제공">불가</dd>
</dl>
</section>
<section id="units">
<h2>객실</h2>
<article>
<h3>A동 스탠다드</h3>
<dl>
<dt>객실 타입</dt><dd>스탠다드</dd>
<dt>기준 인원</dt><dd>2명</dd>
<dt>최대 인원</dt><dd>4명</dd>
<dt>침대 구성</dt><dd>퀸 1</dd>
<dt>주방 여부</dt><dd>가능</dd>
<dt>전망</dt><dd>오션뷰</dd>
<dt>주중 요금</dt><dd data-ld-offer="A동 스탠다드">150,000원</dd>
</dl>
</article>
<article>
<h3>B동 복층</h3>
<dl>
<dt>객실 타입</dt><dd>복층</dd>
<dt>기준 인원</dt><dd>4명</dd>
<dt>최대 인원</dt><dd>6명</dd>
<dt>침대 구성</dt><dd>퀸 1 + 싱글 2</dd>
<dt>주방 여부</dt><dd>가능</dd>
<dt>전망</dt><dd>오션뷰</dd>
<dt>주중 요금</dt><dd data-ld-offer="B동 복층">220,000원</dd>
</dl>
</article>
</section>
<section id="photos">
<h2>사진</h2>
<div class="photos">
<figure>
<button type="button">
<img src="https://picsum.photos/seed/pension-out/1200/800" alt="2층 목조 건물 외관과 앞마당 잔디" data-caption="외관" loading="lazy">
</button>
<figcaption>외관</figcaption>
</figure>
<figure>
<button type="button">
<img src="https://picsum.photos/seed/pension-bed/1200/800" alt="퀸 침대와 창밖으로 바다가 보이는 침실" data-caption="A동 침실" loading="lazy">
</button>
<figcaption>A동 침실</figcaption>
</figure>
<figure>
<button type="button">
<img src="https://picsum.photos/seed/pension-liv/1200/800" alt="소파와 원목 테이블이 있는 거실" data-caption="A동 거실" loading="lazy">
</button>
<figcaption>A동 거실</figcaption>
</figure>
<figure>
<button type="button">
<img src="https://picsum.photos/seed/pension-bbq/1200/800" alt="지붕이 있는 야외 바비큐 공간" data-caption="바비큐장" loading="lazy">
</button>
<figcaption>바비큐장</figcaption>
</figure>
<figure>
<button type="button">
<img src="https://picsum.photos/seed/pension-duo/1200/800" alt="계단으로 이어진 복층 구조의 객실" data-caption="B동 복층" loading="lazy">
</button>
<figcaption>B동 복층</figcaption>
</figure>
</div>
</section>
<section id="faq" class="faq">
<h2>자주 묻는 질문</h2>
<details open>
<summary>체크인·체크아웃은 몇 시인가요?</summary>
<p>체크인은 15:00, 체크아웃은 11:00 입니다.</p>
</details>
<details open>
<summary>반려동물과 함께 갈 수 있나요?</summary>
<p>반려동물 동반은 불가합니다.</p>
</details>
<details open>
<summary>객실에서 취사가 가능한가요?</summary>
<p>취사가 가능하며 모든 객실에 주방이 있습니다.</p>
</details>
<details open>
<summary>바비큐를 이용하려면 얼마인가요?</summary>
<p>바비큐 이용료는 20,000원입니다.</p>
</details>
<details open>
<summary>주차는 가능한가요?</summary>
<p>주차가 가능하며 6대까지 주차할 수 있습니다.</p>
</details>
</section>
</main>
<div class="lightbox" aria-hidden="true">
<button type="button" class="close" aria-label="닫기">&times;</button>
<button type="button" class="nav prev" aria-label="이전 사진">&#8249;</button>
<button type="button" class="nav next" aria-label="다음 사진">&#8250;</button>
<figure>
<img src="" alt="">
<figcaption></figcaption>
</figure>
</div>
<div class="cta"><a class="primary" href="tel:0336720000">전화하기</a><button type="button" data-copy="강원특별자치도 양양군 현북면 하조대3길 11">주소 복사</button></div>
<script>
(function(){try{
var box=document.querySelector('.lightbox');
if(box){
var img=box.querySelector('img'),cap=box.querySelector('figcaption'),i=0,
shots=[].slice.call(document.querySelectorAll('.photos button'));
var show=function(n){i=(n+shots.length)%shots.length;var t=shots[i].querySelector('img');
img.src=t.src;img.alt=t.alt;cap.textContent=t.dataset.caption||t.alt;};
var open=function(n){show(n);box.classList.add('on');document.body.style.overflow='hidden';
box.querySelector('.close').focus();};
var close=function(){box.classList.remove('on');document.body.style.overflow='';
if(shots[i])shots[i].focus();};
shots.forEach(function(b,n){b.addEventListener('click',function(){open(n);});});
box.querySelector('.close').addEventListener('click',close);
box.querySelector('.prev').addEventListener('click',function(){show(i-1);});
box.querySelector('.next').addEventListener('click',function(){show(i+1);});
box.addEventListener('click',function(e){if(e.target===box)close();});
document.addEventListener('keydown',function(e){
if(!box.classList.contains('on'))return;
if(e.key==='Escape')close();
if(e.key==='ArrowLeft')show(i-1);
if(e.key==='ArrowRight')show(i+1);});
var x0=null;
box.addEventListener('touchstart',function(e){x0=e.touches[0].clientX;},{passive:true});
box.addEventListener('touchend',function(e){
if(x0===null)return;var d=e.changedTouches[0].clientX-x0;x0=null;
if(Math.abs(d)>40)show(d<0?i+1:i-1);},{passive:true});
}
var copy=document.querySelector('[data-copy]');
if(copy&&navigator.clipboard){
copy.addEventListener('click',function(){
navigator.clipboard.writeText(copy.dataset.copy).then(function(){
var t=copy.textContent;copy.textContent='복사됨';
setTimeout(function(){copy.textContent=t;},1500);});});
}else if(copy){copy.remove();}
}catch(e){}})();
</script>
</body>
</html>