release: v0.10.0
All checks were successful
ci / build (push) Successful in 42s
release / release (push) Successful in 1m5s

This commit is contained in:
Yun Chan 2026-08-29 21:19:26 +09:00
parent 2b7fa32b5b
commit 6930906583
61 changed files with 15165 additions and 82 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View file

@ -0,0 +1,358 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>철판클럽 가맹 안내 — 운영 조건부터 확인하기</title>
<meta
name="description"
content="가상 버거 브랜드 철판클럽의 상권, 점포, 면적, 개설 절차와 비용 미정 항목을 먼저 살펴보는 가맹 문의 쇼케이스입니다."
/>
<meta property="og:title" content="철판클럽 가맹 안내 — 운영 조건부터" />
<meta
property="og:description"
content="수익 약속 없이 상권, 점포, 주방 동선과 비용 미정 항목을 먼저 보여 주는 국내형 가맹 안내 화면."
/>
<meta property="og:type" content="website" />
<meta property="og:image" content="../og.png" />
<meta property="og:image:alt" content="직원들이 철판 조리와 포장을 나누어 맡는 주방" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="철판클럽 가맹 안내 — 운영 조건부터" />
<meta
name="twitter:description"
content="상권, 점포, 주방 동선과 비용 미정 항목을 먼저 살펴보는 가맹 안내 쇼케이스."
/>
<meta name="twitter:image" content="../og.png" />
<meta name="theme-color" content="#fffdf8" />
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&display=swap"
/>
<link rel="stylesheet" href="../styles/tokens.css?v=1" />
<link rel="stylesheet" href="../styles/site.css?v=1" />
<link rel="stylesheet" href="../styles/franchise.css?v=1" />
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%232147e7'/%3E%3Cpath d='M6 9h20M8 16h16M10 23h12' stroke='%23fffdf8' stroke-width='3' stroke-linecap='square'/%3E%3C/svg%3E"
/>
<script>document.documentElement.classList.add("is-js");</script>
<script src="../scripts/franchise.js?v=1" defer></script>
</head>
<body class="franchise-page">
<a class="skip-link" href="#main">본문으로 건너뛰기</a>
<div class="truth-bar" role="note">
<p>쇼케이스용 가상 브랜드 · 실제 가맹 모집, 상담, 계약을 제공하지 않음</p>
</div>
<header class="site-head">
<div class="wrap head-row franchise-head-row">
<a class="brand" href="../" aria-label="철판클럽 홈으로">
<span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i></span>
<span>철판클럽</span>
</a>
<nav class="head-nav" aria-label="가맹 안내 메뉴">
<a href="#model">운영 방식</a>
<a href="#site-fit">상권과 점포</a>
<a href="#process">개설 절차</a>
<a href="#cost">비용 항목</a>
</nav>
<a class="head-action" href="#inquiry">문의 준비하기</a>
</div>
</header>
<main id="main">
<section class="franchise-hero" aria-labelledby="franchise-hero-heading">
<div class="franchise-hero-copy">
<p class="section-kicker">철판클럽 가맹 안내</p>
<h1 id="franchise-hero-heading">매출 약속보다<br />운영 조건부터.</h1>
<p>
버거 사진만 보고 결정하지 않도록 상권, 점포, 전용 면적, 주방 동선과 아직 정할 수 없는 비용을 순서대로 보여 줍니다.
</p>
<div class="hero-actions">
<a class="button button-light" href="#site-fit">내 조건 살펴보기</a>
<a class="text-link light-link" href="#cost">비용 미정 항목 보기 <span aria-hidden="true">↓</span></a>
</div>
<div class="franchise-truth">
<strong>먼저 확인해 주세요</strong>
<p>실제 가맹 모집이 아닌 디자인 쇼케이스입니다. 금액, 수익, 매장 수와 개점 기간을 꾸며내지 않습니다.</p>
</div>
</div>
<figure class="franchise-hero-media image-shell">
<img
src="../assets/kitchen-flow.webp"
width="1400"
height="933"
alt="직원 두 명이 좁은 주방에서 철판 조리와 포장 작업을 나누어 맡는 모습"
fetchpriority="high"
/>
<span class="image-fallback" aria-hidden="true">주방 운영 동선 장면</span>
<figcaption>생성 이미지 · 실제 매장과 가맹 시설 아님</figcaption>
</figure>
</section>
<section class="fact-rail" aria-label="가맹 안내 원칙">
<div class="wrap fact-rail-grid">
<p><span>01</span> 수익률을 약속하지 않음</p>
<p><span>02</span> 점포를 보기 전 금액을 확정하지 않음</p>
<p><span>03</span> 실제 개인정보를 받지 않음</p>
</div>
</section>
<section class="section model-section" id="model" aria-labelledby="model-heading">
<div class="wrap">
<div class="section-head split-head">
<div class="model-heading-copy"><p class="section-kicker">어떤 운영 방식인지</p><h2 id="model-heading">좁은 주방에서<br />역할을 분명하게.</h2></div>
<p class="section-note">조리, 조립, 포장, 전달이 서로 겹치지 않는 흐름을 먼저 검토합니다. 실제 설비와 인원은 점포마다 달라집니다.</p>
</div>
<div class="flow-board">
<article><span>01</span><h3>재료 준비</h3><p>냉장 보관과 당일 준비 동선을 분리합니다.</p></article>
<article><span>02</span><h3>철판 조리</h3><p>패티와 양파가 움직이는 구간을 짧게 둡니다.</p></article>
<article><span>03</span><h3>조립과 포장</h3><p>주문표, 유산지, 상자를 한 방향에 둡니다.</p></article>
<article><span>04</span><h3>전달과 픽업</h3><p>매장 손님과 배달 픽업이 부딪히지 않게 나눕니다.</p></article>
</div>
<div class="operator-fit">
<div class="operator-heading-copy"><p class="section-kicker">운영자에게 필요한 태도</p><h3>직접 확인하고,<br />같은 품질을 반복하기.</h3></div>
<ul>
<li><strong>현장 참여</strong><span>조리와 응대 흐름을 직접 익힐 준비</span></li>
<li><strong>위생 기록</strong><span>보관, 조리, 마감 상태를 남길 준비</span></li>
<li><strong>지역 이해</strong><span>점심과 저녁 수요가 다른 상권을 살필 준비</span></li>
<li><strong>팀 운영</strong><span>작은 인원에서 역할을 나누고 교육할 준비</span></li>
</ul>
</div>
</div>
</section>
<section class="section site-fit-section" id="site-fit" aria-labelledby="site-fit-heading">
<div class="wrap">
<div class="section-head split-head">
<div class="site-fit-heading-copy"><p class="section-kicker">상권과 점포</p><h2 id="site-fit-heading">주소보다 먼저<br />운영 장면을 봅니다.</h2></div>
<p class="section-note">아래 분류는 적합 판정이 아니라 문의 때 빠뜨리지 않을 조건 목록입니다. 권장 면적 수치는 실제 검토 뒤에만 제시할 수 있습니다.</p>
</div>
<div class="fit-grid">
<article class="fit-main">
<p class="fit-number mono">A</p>
<h3>상권 흐름</h3>
<p>업무가, 주거지, 역 주변, 대학가, 상점가 또는 여러 수요가 섞인 곳인지 먼저 적습니다.</p>
<ul><li>점심과 저녁 수요 시간</li><li>도보와 차량 접근</li><li>포장 대기 공간</li><li>배달 픽업 접근</li></ul>
</article>
<article>
<p class="fit-number mono">B</p><h3>점포 상태</h3><p>점포 없음, 찾는 중, 협의 중, 보유함으로 나눕니다.</p>
</article>
<article>
<p class="fit-number mono">C</p><h3>전용 면적</h3><p>사용자가 입력한 ㎡와 환산한 평을 함께 봅니다. 적합 판정은 하지 않습니다.</p>
</article>
<article>
<p class="fit-number mono">D</p><h3>설비 조건</h3><p>전기, 배기, 급수, 배수와 반입 경로는 점포 자료로 따로 확인합니다.</p>
</article>
<article>
<p class="fit-number mono">E</p><h3>이전 용도</h3><p>음식점, 소매점, 빈 점포 등 이전 사용 상태를 확인합니다.</p>
</article>
</div>
</div>
</section>
<section class="section franchise-process" id="process" aria-labelledby="process-heading-franchise">
<div class="wrap">
<div class="section-head split-head">
<div class="process-heading-copy"><p class="section-kicker">개설 절차</p><h2 id="process-heading-franchise">기간을 꾸미지 않고<br />순서만 보여 줍니다.</h2></div>
<p class="section-note">점포, 자료, 협의와 공사 조건에 따라 기간이 달라집니다. 아래 흐름은 화면 설명용 예시이며 계약이나 개점을 보장하지 않습니다.</p>
</div>
<ol class="franchise-timeline">
<li><span>01</span><div><h3>문의 준비</h3><p>희망 지역과 시작 시기를 정리합니다.</p></div><small>준비 전</small></li>
<li><span>02</span><div><h3>희망 조건 확인</h3><p>상권 종류, 점포 상태와 운영 경험을 봅니다.</p></div><small>자료 필요</small></li>
<li><span>03</span><div><h3>상권과 점포 검토</h3><p>주소가 정해졌다면 면적, 설비와 동선을 확인합니다.</p></div><small>조건에 따라 다름</small></li>
<li><span>04</span><div><h3>정보 제공과 협의</h3><p>확인 가능한 조건과 미정 항목을 나눕니다.</p></div><small>검토 필요</small></li>
<li><span>05</span><div><h3>계약 내용 검토</h3><p>실제 자료와 조건을 받은 뒤 각 항목을 확인합니다.</p></div><small>자동 확정 아님</small></li>
<li><span>06</span><div><h3>설계와 공사 계획</h3><p>주방, 포장대와 픽업 동선을 점포에 맞춥니다.</p></div><small>점포별 다름</small></li>
<li><span>07</span><div><h3>조리와 응대 교육</h3><p>조리 순서와 위생 기록을 함께 익힙니다.</p></div><small>계획 필요</small></li>
<li><span>08</span><div><h3>개점 전 점검</h3><p>설비, 재료, 동선과 운영 준비를 확인합니다.</p></div><small>완료 보장 아님</small></li>
</ol>
</div>
</section>
<section class="section cost-section" id="cost" aria-labelledby="cost-heading">
<div class="wrap">
<div class="cost-head">
<div class="cost-heading-copy"><p class="section-kicker">비용 항목</p><h2 id="cost-heading">합계를 만들 수 없는<br />이유부터 적습니다.</h2></div>
<div class="unknown-cost">
<span>현재 상태</span>
<strong>비용 미정</strong>
<p>점포, 면적, 설비와 계약 자료가 확인되지 않아 금액을 제시하지 않습니다.</p>
</div>
</div>
<div class="cost-table-wrap" role="region" aria-label="비용 항목 표" tabindex="0">
<table class="cost-table">
<caption class="sr-only">가맹 비용 항목과 현재 확인 상태</caption>
<thead><tr><th scope="col">항목</th><th scope="col">현재 상태</th><th scope="col">금액</th><th scope="col">다음 확인</th></tr></thead>
<tbody>
<tr><th scope="row">가맹 관련 비용</th><td>자료 미제공</td><td>미정</td><td>실제 안내 자료 확인</td></tr>
<tr><th scope="row">교육 비용</th><td>범위 미정</td><td>미정</td><td>인원과 교육 범위 확인</td></tr>
<tr><th scope="row">계약 보증 관련 비용</th><td>조건 미정</td><td>미정</td><td>실제 계약 자료 확인</td></tr>
<tr><th scope="row">주방 설비</th><td>점포 필요</td><td>미정</td><td>전기, 배기와 설비 확인</td></tr>
<tr><th scope="row">가구와 집기</th><td>사양 필요</td><td>미정</td><td>좌석과 포장 방식 확인</td></tr>
<tr><th scope="row">인테리어</th><td>면적 필요</td><td>미정</td><td>전용 면적과 현장 확인</td></tr>
<tr><th scope="row">간판과 외부 표시</th><td>건물 조건 필요</td><td>미정</td><td>건물과 지역 조건 확인</td></tr>
<tr><th scope="row">주문 기기</th><td>운영 방식 필요</td><td>미정</td><td>주문과 결제 방식 확인</td></tr>
<tr><th scope="row">허가와 신고</th><td>지역 조건 필요</td><td>미정</td><td>관할 안내 확인</td></tr>
<tr><th scope="row">첫 물류</th><td>메뉴 범위 필요</td><td>미정</td><td>품목과 보관량 확인</td></tr>
<tr><th scope="row">임대 보증 관련 비용</th><td>점포 계약 필요</td><td>미정</td><td>임대 조건 직접 확인</td></tr>
</tbody>
<tfoot><tr><th scope="row" colspan="2">합계</th><td colspan="2">계산하지 않음 · 확인되지 않은 숫자를 더하지 않습니다</td></tr></tfoot>
</table>
</div>
<p class="cost-warning">평균 매출, 예상 수익, 회수 기간과 최저 비용은 근거 자료가 없어 제공하지 않습니다.</p>
</div>
</section>
<section class="section inquiry-section" id="inquiry" aria-labelledby="inquiry-heading">
<div class="wrap inquiry-layout">
<div class="inquiry-intro">
<p class="section-kicker">문의 준비 체험</p>
<h2 id="inquiry-heading">연락처 없이도<br />조건부터 정리하기.</h2>
<p>실제 이름과 연락처를 받지 않습니다. 화면 안에서 상권과 점포 조건을 정리하고 전송 전 검토 상태를 체험합니다.</p>
<div class="privacy-note" role="note">
<strong>실제 개인정보를 입력하지 마세요.</strong>
<p>입력 내용은 서버로 보내거나 저장하지 않습니다. 성공 화면도 실제 접수가 아닙니다.</p>
</div>
</div>
<div class="inquiry-panel">
<ol class="step-indicator" aria-label="문의 준비 단계">
<li class="is-current" data-step-indicator="1" aria-current="step"><span>1</span><small>상권</small></li>
<li data-step-indicator="2"><span>2</span><small>점포</small></li>
<li data-step-indicator="3"><span>3</span><small>검토</small></li>
</ol>
<form id="franchise-form" novalidate>
<section class="form-step" data-step="1" aria-labelledby="step-one-heading">
<p class="step-count">1 / 3</p>
<h3 id="step-one-heading">어느 지역에서 시작할까요?</h3>
<div class="form-field">
<label for="demo-name">예시 호칭</label>
<input id="demo-name" name="demoName" type="text" minlength="2" autocomplete="off" placeholder="실명 대신 예시 호칭" required />
<small>실제 이름을 입력하지 마세요.</small>
<p class="field-error" id="demo-name-error"></p>
</div>
<div class="form-field">
<label for="region">희망 지역</label>
<select id="region" name="region" required>
<option value="">지역 고르기</option><option>서울 동쪽</option><option>서울 서쪽</option><option>서울 남쪽</option><option>서울 북쪽</option><option>수도권</option><option>아직 모름</option>
</select>
<p class="field-error" id="region-error"></p>
</div>
<fieldset class="form-field">
<legend>희망 상권</legend>
<div class="radio-grid">
<label><input type="radio" name="areaKind" value="업무가" required /><span>업무가</span></label>
<label><input type="radio" name="areaKind" value="주거지" /><span>주거지</span></label>
<label><input type="radio" name="areaKind" value="역 주변" /><span>역 주변</span></label>
<label><input type="radio" name="areaKind" value="대학가" /><span>대학가</span></label>
<label><input type="radio" name="areaKind" value="상점가" /><span>상점가</span></label>
<label><input type="radio" name="areaKind" value="아직 모름" /><span>아직 모름</span></label>
</div>
<p class="field-error" id="area-kind-error"></p>
</fieldset>
<div class="form-actions"><button class="button button-primary" type="button" data-next>점포 조건으로</button></div>
</section>
<section class="form-step" data-step="2" aria-labelledby="step-two-heading">
<p class="step-count">2 / 3</p>
<h3 id="step-two-heading">점포는 어느 단계인가요?</h3>
<fieldset class="form-field">
<legend>점포 상태</legend>
<div class="radio-grid">
<label><input type="radio" name="storePossession" value="아직 없음" required /><span>아직 없음</span></label>
<label><input type="radio" name="storePossession" value="찾는 중" /><span>찾는 중</span></label>
<label><input type="radio" name="storePossession" value="협의 중" /><span>협의 중</span></label>
<label><input type="radio" name="storePossession" value="보유함" /><span>보유함</span></label>
</div>
<p class="field-error" id="store-possession-error"></p>
</fieldset>
<div class="form-field area-field" id="area-field" hidden>
<label for="exclusive-area">전용 면적</label>
<div class="unit-input"><input id="exclusive-area" name="exclusiveArea" type="number" min="0.1" step="0.1" inputmode="decimal" /><span>㎡</span></div>
<output id="area-conversion" for="exclusive-area">면적을 입력하면 평으로도 보여 드립니다.</output>
<small>환산값은 적합 판정이 아닙니다.</small>
<p class="field-error" id="exclusive-area-error"></p>
</div>
<div class="form-field">
<label for="opening-window">희망 시작 시기</label>
<select id="opening-window" name="openingWindow" required>
<option value="">시기 고르기</option><option>석 달 안</option><option>석 달에서 여섯 달</option><option>여섯 달에서 열두 달</option><option>아직 모름</option>
</select>
<p class="field-error" id="opening-window-error"></p>
</div>
<fieldset class="form-field">
<legend>매장 운영 경험</legend>
<div class="radio-grid radio-grid-two">
<label><input type="radio" name="experience" value="있음" required /><span>있음</span></label>
<label><input type="radio" name="experience" value="없음" /><span>없음</span></label>
</div>
<p class="field-error" id="experience-error"></p>
</fieldset>
<div class="form-actions split-actions"><button class="button button-ghost" type="button" data-back>이전</button><button class="button button-primary" type="button" data-next>입력 내용 검토</button></div>
</section>
<section class="form-step" data-step="3" aria-labelledby="step-three-heading">
<p class="step-count">3 / 3</p>
<h3 id="step-three-heading">보내기 전에 확인하세요.</h3>
<dl class="review-list" id="review-list">
<div><dt>예시 호칭</dt><dd data-review="demoName">—</dd></div>
<div><dt>희망 지역</dt><dd data-review="region">—</dd></div>
<div><dt>희망 상권</dt><dd data-review="areaKind">—</dd></div>
<div><dt>점포 상태</dt><dd data-review="storePossession">—</dd></div>
<div><dt>전용 면적</dt><dd data-review="exclusiveArea">해당 없음</dd></div>
<div><dt>희망 시기</dt><dd data-review="openingWindow">—</dd></div>
<div><dt>운영 경험</dt><dd data-review="experience">—</dd></div>
</dl>
<label class="consent-check"><input type="checkbox" id="demo-consent" required /><span>실제 문의가 아니며 개인정보를 입력하지 않았음을 확인했습니다.</span></label>
<p class="field-error" id="demo-consent-error"></p>
<div class="form-actions split-actions"><button class="button button-ghost" type="button" data-back>수정하기</button><button class="button button-primary" type="submit" id="submit-inquiry">문의 준비 마치기</button></div>
<p class="submit-state" id="submit-state" role="alert"></p>
</section>
</form>
<section class="inquiry-success" id="inquiry-success" hidden aria-labelledby="success-heading" tabindex="-1">
<span class="success-mark" aria-hidden="true">✓</span>
<p class="section-kicker">화면 체험 완료</p>
<h3 id="success-heading">문의 준비가 끝났어요.</h3>
<p>제출 절차를 보여 주는 예시이며 실제 담당자에게 전송되지 않았습니다.</p>
<div class="success-actions"><button class="button button-ghost" type="button" id="review-again">입력 내용 다시 보기</button><button class="button button-primary" type="button" id="restart-form">처음부터 보기</button></div>
</section>
</div>
</div>
</section>
<section class="franchise-close" aria-labelledby="close-heading">
<div class="wrap close-row">
<div class="close-heading-copy"><p class="section-kicker">철판클럽</p><h2 id="close-heading">숫자를 꾸미지 않아야<br />조건을 제대로 볼 수 있습니다.</h2></div>
<a class="button button-light" href="../">소비자 홈으로 돌아가기</a>
</div>
</section>
</main>
<footer class="site-foot">
<div class="wrap foot-grid franchise-foot-grid">
<div><a class="brand brand-foot" href="../"><span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i></span><span>철판클럽</span></a><p>눌러 굽고, 바로 건넨다.</p></div>
<div><strong>가맹 안내</strong><a href="#model">운영 방식</a><a href="#site-fit">상권과 점포</a><a href="#process">개설 절차</a><a href="#cost">비용 항목</a></div>
<div class="foot-truth"><strong>쇼케이스 고지</strong><p>가상 브랜드이며 실제 가맹 모집, 수익 안내, 계약과 문의 전송을 제공하지 않습니다.</p><p class="mono">DESIGN SHOWCASE · 2026</p></div>
</div>
</footer>
</body>
</html>

View file

@ -0,0 +1,462 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>철판클럽 — 눌러 굽고, 바로 건넨다</title>
<meta
name="description"
content="서울의 빠른 한 끼를 위한 가상 눌림 버거 브랜드 철판클럽의 메뉴, 조합기, 예시 매장과 운영 방식을 살펴보세요."
/>
<meta property="og:title" content="철판클럽 — 눌러 굽고, 바로 건넨다" />
<meta
property="og:description"
content="메뉴를 고르고, 재료를 조합하고, 예시 매장 운영 상태까지 확인하는 국내형 버거 쇼케이스."
/>
<meta property="og:type" content="website" />
<meta property="og:image" content="./og.png" />
<meta property="og:image:alt" content="철판 주방에서 포장한 버거를 건네는 장면" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="철판클럽 — 눌러 굽고, 바로 건넨다" />
<meta
name="twitter:description"
content="메뉴를 고르고 재료를 조합해 보는 국내형 버거 브랜드 쇼케이스."
/>
<meta name="twitter:image" content="./og.png" />
<meta name="theme-color" content="#f4f1e9" />
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&display=swap"
/>
<link rel="stylesheet" href="./styles/tokens.css?v=1" />
<link rel="stylesheet" href="./styles/site.css?v=1" />
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%232147e7'/%3E%3Cpath d='M6 9h20M8 16h16M10 23h12' stroke='%23fffdf8' stroke-width='3' stroke-linecap='square'/%3E%3C/svg%3E"
/>
<script>document.documentElement.classList.add("is-js");</script>
<script src="./scripts/site.js?v=1" defer></script>
</head>
<body>
<a class="skip-link" href="#main">본문으로 건너뛰기</a>
<div class="truth-bar" role="note">
<p>쇼케이스용 가상 브랜드 · 가격과 매장 정보는 화면 동작 확인용 예시</p>
</div>
<header class="site-head" id="top">
<div class="wrap head-row">
<a class="brand" href="#top" aria-label="철판클럽 처음으로">
<span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i></span>
<span>철판클럽</span>
</a>
<nav class="head-nav" aria-label="주요 메뉴">
<a href="#menu">메뉴</a>
<a href="#builder">조합하기</a>
<a href="#stores">매장 찾기</a>
<a href="./franchise/">가맹 안내</a>
</nav>
<a class="head-action" href="#stores">가까운 매장 보기</a>
</div>
</header>
<main id="main">
<section class="hero" aria-labelledby="hero-heading">
<div class="hero-copy">
<p class="section-kicker">서울 도심 눌림 버거</p>
<h1 id="hero-heading">눌러 굽고,<br />바로 건넨다.</h1>
<p class="hero-lead">
얇게 눌러 가장자리는 바삭하게. 주문 뒤 조립하고 포장해, 퇴근길 한 상자로 건넵니다.
</p>
<div class="hero-actions">
<a class="button button-primary" href="#menu">오늘 메뉴 보기</a>
<a class="text-link" href="#builder">내 버거 조합하기 <span aria-hidden="true">→</span></a>
</div>
<dl class="hero-facts">
<div><dt>굽기</dt><dd>얇은 패티를 주문 뒤 눌러 굽기</dd></div>
<div><dt>받기</dt><dd>포장과 배달 가능 여부를 매장별 표시</dd></div>
</dl>
</div>
<figure class="hero-media image-shell">
<picture>
<source media="(max-width: 820px)" srcset="./assets/hero-handoff-mobile.webp" />
<img
src="./assets/hero-handoff.webp"
width="1600"
height="900"
alt="밝은 철판 주방에서 직원이 포장한 버거를 건네는 모습"
fetchpriority="high"
/>
</picture>
<span class="image-fallback" aria-hidden="true">버거 포장 장면</span>
<figcaption>
<span>철판에서 상자까지</span>
<span>생성 이미지 · 실제 매장 아님</span>
</figcaption>
</figure>
<aside class="hero-order" aria-label="오늘 고르기">
<span class="order-index">01</span>
<p>철판 더블</p>
<strong>예시 단품 8,900원</strong>
<button type="button" data-preset="double">이 조합으로 시작</button>
</aside>
</section>
<section class="service-strip" aria-label="이용 방식">
<div class="wrap service-row">
<p><span aria-hidden="true">●</span> 주문 뒤 조리</p>
<p>단품과 세트</p>
<p>포장 여부 매장별 확인</p>
<p>배달 여부 매장별 확인</p>
<p class="mono">GRIDDLE CLUB</p>
</div>
</section>
<section class="section menu-section" id="menu" aria-labelledby="menu-heading">
<div class="wrap">
<div class="section-head split-head">
<div>
<p class="section-kicker">오늘 고를 메뉴</p>
<h2 id="menu-heading">버거가 먼저 보이게.</h2>
</div>
<p class="section-note">
모든 가격은 조합기 동작을 보여 주기 위한 예시입니다. 실제 판매와 주문은 이루어지지 않습니다.
</p>
</div>
<div class="menu-toolbar" role="group" aria-label="메뉴 분류">
<button type="button" class="filter-button is-active" data-menu-filter="all" aria-pressed="true">전체</button>
<button type="button" class="filter-button" data-menu-filter="burger" aria-pressed="false">버거</button>
<button type="button" class="filter-button" data-menu-filter="side" aria-pressed="false">곁들임</button>
<button type="button" class="filter-button" data-menu-filter="drink" aria-pressed="false">음료</button>
</div>
<div class="menu-grid" id="menu-grid">
<article class="menu-item menu-item-featured" data-menu-category="burger">
<div class="menu-photo image-shell">
<img
src="./assets/signature-double.webp"
width="1400"
height="933"
alt="철판 패티 두 장과 치즈, 구운 양파를 넣은 버거"
loading="lazy"
/>
<span class="image-fallback" aria-hidden="true">철판 더블 사진</span>
<span class="menu-number">01</span>
</div>
<div class="menu-body">
<div><p class="menu-state">대표 메뉴</p><h3>철판 더블</h3></div>
<p>얇은 패티 두 장, 치즈, 구운 양파, 피클, 집 소스.</p>
<div class="menu-foot">
<span><small>예시 단품</small> 8,900원</span>
<button type="button" data-preset="double">조합하기</button>
</div>
</div>
</article>
<article class="menu-item menu-item-graphic" data-menu-category="burger">
<div class="ingredient-stack" aria-hidden="true">
<span class="bun"></span><span class="pickle"></span><span class="cheese"></span><span class="patty"></span><span class="bun bottom"></span>
<b>02</b>
</div>
<div class="menu-body">
<div><p class="menu-state">가볍게</p><h3>철판 싱글</h3></div>
<p>패티 한 장, 치즈, 피클, 집 소스. 단품으로 빠르게.</p>
<div class="menu-foot">
<span><small>예시 단품</small> 6,900원</span>
<button type="button" data-preset="single">조합하기</button>
</div>
</div>
</article>
<article class="menu-item menu-item-blue" data-menu-category="burger">
<div class="menu-poster" aria-hidden="true"><span>03</span><i></i><i></i><i></i></div>
<div class="menu-body">
<div><p class="menu-state">매운 피클</p><h3>고추 피클 더블</h3></div>
<p>고추 피클과 매운 소스를 더한 더블. 맵기는 예시입니다.</p>
<div class="menu-foot">
<span><small>예시 단품</small> 9,400원</span>
<button type="button" data-preset="spicy">조합하기</button>
</div>
</div>
</article>
<article class="menu-item menu-item-small" data-menu-category="side">
<div class="menu-body">
<div><p class="menu-state">곁들임</p><h3>얇은 감자</h3></div>
<p>가늘게 튀겨 소금으로 마무리합니다.</p>
<div class="menu-foot"><span><small>예시 가격</small> 3,600원</span></div>
</div>
</article>
<article class="menu-item menu-item-small" data-menu-category="side">
<div class="menu-body">
<div><p class="menu-state">곁들임</p><h3>양파 한 컵</h3></div>
<p>철판에서 단맛을 낸 양파를 따로 담습니다.</p>
<div class="menu-foot"><span><small>예시 가격</small> 2,800원</span></div>
</div>
</article>
<article class="menu-item menu-item-small" data-menu-category="drink">
<div class="menu-body">
<div><p class="menu-state">음료</p><h3>탄산 한 잔</h3></div>
<p>매장에서 고른 음료 한 잔. 제품별 성분을 확인해 주세요.</p>
<div class="menu-foot"><span><small>예시 가격</small> 2,400원</span></div>
</div>
</article>
</div>
<div class="empty-state" id="menu-empty" hidden role="status">
<strong>이 분류에 준비된 예시 메뉴가 없어요.</strong>
<button type="button" data-show-all-menu>모든 메뉴 보기</button>
</div>
</div>
</section>
<section class="section builder-section" id="builder" aria-labelledby="builder-heading">
<div class="wrap">
<div class="section-head split-head">
<div>
<p class="section-kicker">버거 조합기</p>
<h2 id="builder-heading">상자에 담길 내용을<br />먼저 정하세요.</h2>
</div>
<p class="section-note">재료 선택과 예시 총액은 실제로 바뀝니다. 주문 전송 기능은 없습니다.</p>
</div>
<div class="builder-layout">
<form class="builder-form" id="burger-builder" novalidate>
<fieldset>
<legend><span>01</span> 맛 고르기</legend>
<div class="choice-row">
<label><input type="radio" name="flavor" value="classic" checked /><span><strong>기본</strong><small>집 소스와 피클</small></span></label>
<label><input type="radio" name="flavor" value="spicy" /><span><strong>매운 피클</strong><small>고추 피클과 매운 소스</small></span></label>
</div>
</fieldset>
<fieldset>
<legend><span>02</span> 크기와 치즈</legend>
<div class="choice-row choice-row-compact">
<label><input type="radio" name="patty" value="1" /><span>패티 한 장</span></label>
<label><input type="radio" name="patty" value="2" checked /><span>패티 두 장</span></label>
</div>
<label class="field-label" for="cheese">치즈</label>
<select id="cheese" name="cheese">
<option value="0">치즈 없음</option>
<option value="1" selected>치즈 한 장</option>
<option value="2">치즈 두 장</option>
</select>
</fieldset>
<fieldset>
<legend><span>03</span> 양파와 피클</legend>
<div class="select-grid">
<label>구운 양파
<select name="onion"><option value="none">없음</option><option value="regular" selected>기본</option><option value="extra">많이</option></select>
</label>
<label>피클
<select name="pickle"><option value="none">없음</option><option value="regular" selected>기본</option><option value="extra">많이</option></select>
</label>
<label>소스
<select name="sauce"><option value="none">없음</option><option value="regular" selected>기본</option><option value="side">따로</option></select>
</label>
</div>
</fieldset>
<fieldset>
<legend><span>04</span> 단품 또는 세트</legend>
<div class="choice-row choice-row-compact">
<label><input type="radio" name="serving" value="single" checked /><span>단품</span></label>
<label><input type="radio" name="serving" value="set" /><span>세트 더하기</span></label>
</div>
<div class="set-options" id="set-options">
<p id="set-heading">세트 구성</p>
<div class="select-grid">
<label>곁들임
<select name="side" disabled><option value="fries">얇은 감자</option><option value="onion">양파 한 컵</option></select>
</label>
<label>음료
<select name="drink" disabled><option value="cola">탄산 한 잔</option><option value="water">물 한 병</option></select>
</label>
</div>
</div>
</fieldset>
<fieldset>
<legend><span>05</span> 받을 예시 매장</legend>
<label class="field-label" for="builder-store">매장</label>
<select id="builder-store" name="store">
<option value="seongsu">성수 예시점 · 영업 중 예시</option>
<option value="yeonnam">연남 예시점 · 영업 종료 예시</option>
<option value="mullae">문래 예시점 · 오늘은 쉬어요</option>
</select>
<p class="field-help">매장과 영업 상태도 화면 동작용 예시입니다.</p>
</fieldset>
</form>
<aside class="builder-summary" aria-labelledby="summary-heading">
<div class="summary-photo image-shell">
<img
src="./assets/signature-double.webp"
width="1400"
height="933"
alt="조합 결과를 설명하는 철판 더블 버거"
loading="lazy"
/>
<span class="image-fallback" aria-hidden="true">조합 버거 사진</span>
<span class="summary-stamp">화면 예시</span>
</div>
<div class="summary-body">
<p class="section-kicker">고른 내용</p>
<h3 id="summary-heading">철판 더블</h3>
<p id="summary-lines">패티 두 장 / 치즈 한 장 / 양파 기본 / 피클 기본 / 소스 기본</p>
<dl class="summary-list">
<div><dt>받기</dt><dd id="summary-serving">단품</dd></div>
<div><dt>예시 매장</dt><dd id="summary-store">성수 예시점</dd></div>
<div><dt>상태</dt><dd id="summary-store-state">영업 중 예시</dd></div>
</dl>
<div class="allergen-note" id="allergen-note">
<strong>알레르기 예시</strong>
<p id="summary-allergens">알, 우유, 대두, 밀, 쇠고기</p>
<small>같은 철판과 조리 도구를 함께 씁니다. 재료를 빼도 알레르기 안전을 보장하지 않습니다.</small>
</div>
<div class="summary-total"><span>화면 계산용 예시 총액</span><strong id="summary-total">8,900원</strong></div>
<p class="truth-inline">실제 주문이나 결제로 전송되지 않습니다.</p>
<button class="button button-primary button-full" type="button" id="save-builder">선택 내용 저장</button>
<a class="button button-ghost button-full" href="#stores" id="other-store-link">다른 매장 찾기</a>
<p class="save-status" id="save-status" role="status" aria-live="polite"></p>
</div>
</aside>
</div>
</div>
</section>
<section class="section process-section" id="process" aria-labelledby="process-heading">
<div class="wrap process-grid">
<div class="process-copy">
<p class="section-kicker">철판에서 상자까지</p>
<h2 id="process-heading">빠르다는 말 대신,<br />순서를 보여 줍니다.</h2>
<ol class="process-list">
<li><span>01</span><div><strong>누르기</strong><p>얇은 패티를 뜨거운 철판에 눌러 가장자리를 만듭니다.</p></div></li>
<li><span>02</span><div><strong>겹치기</strong><p>치즈, 구운 양파, 피클을 고른 순서대로 올립니다.</p></div></li>
<li><span>03</span><div><strong>건네기</strong><p>유산지와 상자에 담고 포장 상태를 확인합니다.</p></div></li>
</ol>
</div>
<figure class="process-media image-shell">
<img
src="./assets/griddle-press.webp"
width="1400"
height="933"
alt="조리사가 철판에서 패티를 눌러 굽고 양파를 익히는 모습"
loading="lazy"
/>
<span class="image-fallback" aria-hidden="true">철판 조리 장면</span>
<figcaption>생성 이미지 · 실제 조리 시설 아님</figcaption>
</figure>
</div>
</section>
<section class="section stores-section" id="stores" aria-labelledby="stores-heading">
<div class="wrap">
<div class="section-head split-head">
<div><p class="section-kicker">매장 찾기</p><h2 id="stores-heading">지금 받을 수 있는지<br />먼저 확인하세요.</h2></div>
<p class="section-note">주소, 전화, 거리와 운영 상태를 꾸며내지 않습니다. 아래 세 곳은 상태 화면을 위한 예시 매장입니다.</p>
</div>
<div class="store-tools">
<div class="search-box">
<input id="store-search" type="search" placeholder="동네 또는 매장명 검색" autocomplete="off" aria-label="예시 매장 검색" />
<button type="button" id="clear-store-search" hidden>지우기</button>
</div>
<div class="region-filters" role="group" aria-label="지역 고르기">
<button type="button" class="is-active" data-region="all" aria-pressed="true">전체</button>
<button type="button" data-region="성동" aria-pressed="false">성동</button>
<button type="button" data-region="마포" aria-pressed="false">마포</button>
<button type="button" data-region="영등포" aria-pressed="false">영등포</button>
</div>
</div>
<div class="store-layout">
<div class="store-list" id="store-list">
<article class="store-card is-selected" data-store="seongsu" data-region="성동" data-search="성수 예시점 성동 성수동" tabindex="-1">
<div class="store-card-top"><span class="store-state state-open">영업 중 예시</span><span>예시 매장</span></div>
<h3>성수 예시점</h3>
<p>서울 성동구 성수동 일대 · 상세 주소 없음</p>
<dl><div><dt>운영</dt><dd>예시 11:00–21:30</dd></div><div><dt>마지막 주문</dt><dd>예시 21:00</dd></div></dl>
<ul aria-label="이용 방식"><li>포장 가능 예시</li><li>배달 가능 예시</li></ul>
<button type="button" data-select-store="seongsu">이 매장으로 조합 보기</button>
</article>
<article class="store-card" data-store="yeonnam" data-region="마포" data-search="연남 예시점 마포 연남동" tabindex="-1">
<div class="store-card-top"><span class="store-state state-closed">영업 종료 예시</span><span>예시 매장</span></div>
<h3>연남 예시점</h3>
<p>서울 마포구 연남동 일대 · 상세 주소 없음</p>
<dl><div><dt>운영</dt><dd>예시 11:30–20:30</dd></div><div><dt>마지막 주문</dt><dd>오늘 마감</dd></div></dl>
<ul aria-label="이용 방식"><li>포장 마감</li><li>배달 마감</li></ul>
<button type="button" data-select-store="yeonnam">종료 상태로 조합 보기</button>
</article>
<article class="store-card" data-store="mullae" data-region="영등포" data-search="문래 예시점 영등포 문래동" tabindex="-1">
<div class="store-card-top"><span class="store-state state-pause">오늘은 쉬어요</span><span>예시 매장</span></div>
<h3>문래 예시점</h3>
<p>서울 영등포구 문래동 일대 · 상세 주소 없음</p>
<dl><div><dt>운영</dt><dd>임시 휴무 예시</dd></div><div><dt>다음 확인</dt><dd>매장별 안내 필요</dd></div></dl>
<ul aria-label="이용 방식"><li>포장 불가</li><li>배달 불가</li></ul>
<button type="button" data-select-store="mullae">휴무 상태로 조합 보기</button>
</article>
</div>
<aside class="store-map" aria-label="예시 매장 분포 그림">
<div class="map-grid" aria-hidden="true"></div>
<span class="map-river" aria-hidden="true"></span>
<button type="button" class="map-pin pin-seongsu is-active" data-map-store="seongsu" aria-label="성수 예시점 고르기"><span>성수</span></button>
<button type="button" class="map-pin pin-yeonnam" data-map-store="yeonnam" aria-label="연남 예시점 고르기"><span>연남</span></button>
<button type="button" class="map-pin pin-mullae" data-map-store="mullae" aria-label="문래 예시점 고르기"><span>문래</span></button>
<p>실제 지도와 위치가 아닌 화면 설명용 도식</p>
</aside>
</div>
<div class="empty-state store-empty" id="store-empty" hidden role="status">
<strong>찾는 조건과 맞는 예시 매장이 없어요.</strong>
<p>검색어와 지역 조건을 지우고 다시 확인해 주세요.</p>
<button type="button" id="reset-store-search">검색 조건 지우기</button>
</div>
</div>
</section>
<section class="franchise-callout" aria-labelledby="franchise-heading">
<figure class="franchise-image image-shell">
<img
src="./assets/kitchen-flow.webp"
width="1400"
height="933"
alt="좁고 밝은 주방에서 두 직원이 철판 조리와 포장을 나누어 맡는 모습"
loading="lazy"
/>
<span class="image-fallback" aria-hidden="true">주방 운영 동선 장면</span>
</figure>
<div class="franchise-copy">
<p class="section-kicker">가맹을 생각한다면</p>
<h2 id="franchise-heading">매출 약속보다<br />운영 조건부터.</h2>
<p>상권, 점포, 면적, 조리 동선과 비용 미정 항목을 먼저 확인할 수 있게 분리했습니다.</p>
<a class="button button-light" href="./franchise/">가맹 운영 조건 보기 <span aria-hidden="true">→</span></a>
<small>실제 가맹 모집과 상담이 아닌 쇼케이스용 흐름입니다.</small>
</div>
</section>
</main>
<footer class="site-foot">
<div class="wrap foot-grid">
<div><a class="brand brand-foot" href="#top"><span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i></span><span>철판클럽</span></a><p>눌러 굽고, 바로 건넨다.</p></div>
<div><strong>살펴보기</strong><a href="#menu">메뉴</a><a href="#builder">조합기</a><a href="#stores">예시 매장</a><a href="./franchise/">가맹 안내</a></div>
<div><strong>정보 안내</strong><p>알레르기 정보도 예시입니다.</p><p>원산지와 영양 정보는 실제 제품 자료가 아닙니다.</p></div>
<div class="foot-truth"><strong>쇼케이스 고지</strong><p>가상 브랜드이며 실제 주문, 결제, 매장 방문, 가맹 문의를 제공하지 않습니다.</p><p class="mono">DESIGN SHOWCASE · 2026</p></div>
</div>
</footer>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 KiB

View file

@ -0,0 +1,300 @@
(() => {
"use strict";
const fixture = new URLSearchParams(window.location.search).get("fixture") || "";
const form = document.querySelector("#franchise-form");
const formSteps = [...document.querySelectorAll("[data-step]")];
const indicators = [...document.querySelectorAll("[data-step-indicator]")];
const indicatorList = document.querySelector(".step-indicator");
const areaField = document.querySelector("#area-field");
const areaInput = document.querySelector("#exclusive-area");
const areaOutput = document.querySelector("#area-conversion");
const successPanel = document.querySelector("#inquiry-success");
const submitState = document.querySelector("#submit-state");
const submitButton = document.querySelector("#submit-inquiry");
let currentStep = 1;
const markBrokenImage = (image) => {
const shell = image.closest(".image-shell");
if (shell) shell.classList.add("is-broken");
};
document.querySelectorAll(".image-shell img").forEach((image) => {
image.addEventListener("error", () => markBrokenImage(image));
if (image.complete && image.naturalWidth === 0) markBrokenImage(image);
});
if (!form) return;
const getRadioValue = (name) => form.querySelector(`input[name="${name}"]:checked`)?.value || "";
const getSelectValue = (name) => {
const field = form.elements.namedItem(name);
return field instanceof HTMLSelectElement ? field.value : "";
};
const getInputValue = (name) => {
const field = form.elements.namedItem(name);
return field instanceof HTMLInputElement ? field.value.trim() : "";
};
const setStep = (step) => {
currentStep = step;
formSteps.forEach((section) => section.classList.toggle("is-current", Number(section.dataset.step) === step));
indicators.forEach((item) => {
const active = Number(item.dataset.stepIndicator) === step;
item.classList.toggle("is-current", active);
if (active) item.setAttribute("aria-current", "step");
else item.removeAttribute("aria-current");
});
const heading = form.querySelector(`[data-step="${step}"] h3`);
if (heading instanceof HTMLElement) {
heading.setAttribute("tabindex", "-1");
heading.focus({ preventScroll: true });
}
};
const clearFieldError = (fieldId, errorId) => {
const field = document.querySelector(`#${fieldId}`);
const error = document.querySelector(`#${errorId}`);
const wrapper = field?.closest(".form-field");
wrapper?.classList.remove("has-error");
field?.removeAttribute("aria-invalid");
field?.removeAttribute("aria-describedby");
if (error) error.textContent = "";
};
const setFieldError = (fieldId, errorId, message) => {
const field = document.querySelector(`#${fieldId}`);
const error = document.querySelector(`#${errorId}`);
const wrapper = field?.closest(".form-field");
wrapper?.classList.add("has-error");
field?.setAttribute("aria-invalid", "true");
field?.setAttribute("aria-describedby", errorId);
if (error) error.textContent = message;
return field;
};
const clearRadioError = (name, errorId) => {
const radios = [...form.querySelectorAll(`input[name="${name}"]`)];
const wrapper = radios[0]?.closest(".form-field");
wrapper?.classList.remove("has-error");
radios.forEach((radio) => {
radio.removeAttribute("aria-invalid");
radio.removeAttribute("aria-describedby");
});
const error = document.querySelector(`#${errorId}`);
if (error) error.textContent = "";
};
const setRadioError = (name, errorId, message) => {
const radios = [...form.querySelectorAll(`input[name="${name}"]`)];
const wrapper = radios[0]?.closest(".form-field");
wrapper?.classList.add("has-error");
radios.forEach((radio) => {
radio.setAttribute("aria-invalid", "true");
radio.setAttribute("aria-describedby", errorId);
});
const error = document.querySelector(`#${errorId}`);
if (error) error.textContent = message;
return radios[0];
};
const validateStepOne = () => {
const invalid = [];
clearFieldError("demo-name", "demo-name-error");
clearFieldError("region", "region-error");
clearRadioError("areaKind", "area-kind-error");
if (getInputValue("demoName").length < 2) {
invalid.push(setFieldError("demo-name", "demo-name-error", "예시 호칭을 두 글자 이상 입력해 주세요."));
}
if (!getSelectValue("region")) {
invalid.push(setFieldError("region", "region-error", "희망 지역을 골라 주세요."));
}
if (!getRadioValue("areaKind")) {
invalid.push(setRadioError("areaKind", "area-kind-error", "희망 상권을 골라 주세요."));
}
invalid[0]?.focus();
return invalid.length === 0;
};
const requiresArea = () => ["협의 중", "보유함"].includes(getRadioValue("storePossession"));
const updateAreaField = () => {
const required = requiresArea();
if (areaField) areaField.hidden = !required;
if (areaInput instanceof HTMLInputElement) {
areaInput.required = required;
if (!required) {
areaInput.value = "";
clearFieldError("exclusive-area", "exclusive-area-error");
if (areaOutput) areaOutput.textContent = "면적을 입력하면 평으로도 보여 드립니다.";
}
}
};
const updateAreaConversion = () => {
if (!(areaInput instanceof HTMLInputElement) || !areaOutput) return;
const squareMeters = Number(areaInput.value);
if (!Number.isFinite(squareMeters) || squareMeters <= 0) {
areaOutput.textContent = "면적을 입력하면 평으로도 보여 드립니다.";
return;
}
const pyeong = Math.round((squareMeters / 3.3058) * 10) / 10;
areaOutput.textContent = `${squareMeters.toLocaleString("ko-KR")}㎡ · 약 ${pyeong.toLocaleString("ko-KR")}평`;
};
const validateStepTwo = () => {
const invalid = [];
clearRadioError("storePossession", "store-possession-error");
clearFieldError("exclusive-area", "exclusive-area-error");
clearFieldError("opening-window", "opening-window-error");
clearRadioError("experience", "experience-error");
if (!getRadioValue("storePossession")) {
invalid.push(setRadioError("storePossession", "store-possession-error", "점포 상태를 골라 주세요."));
}
if (requiresArea()) {
const value = Number(getInputValue("exclusiveArea"));
if (!Number.isFinite(value) || value <= 0) {
invalid.push(setFieldError("exclusive-area", "exclusive-area-error", "면적은 0보다 큰 숫자로 입력해 주세요."));
}
}
if (!getSelectValue("openingWindow")) {
invalid.push(setFieldError("opening-window", "opening-window-error", "희망 시작 시기를 골라 주세요."));
}
if (!getRadioValue("experience")) {
invalid.push(setRadioError("experience", "experience-error", "운영 경험 여부를 골라 주세요."));
}
invalid[0]?.focus();
return invalid.length === 0;
};
const reviewValues = () => {
const squareMeters = Number(getInputValue("exclusiveArea"));
const areaLabel = requiresArea() && squareMeters > 0
? `${squareMeters.toLocaleString("ko-KR")}㎡ · 약 ${(Math.round((squareMeters / 3.3058) * 10) / 10).toLocaleString("ko-KR")}평`
: "해당 없음";
const values = {
demoName: getInputValue("demoName"),
region: getSelectValue("region"),
areaKind: getRadioValue("areaKind"),
storePossession: getRadioValue("storePossession"),
exclusiveArea: areaLabel,
openingWindow: getSelectValue("openingWindow"),
experience: getRadioValue("experience"),
};
Object.entries(values).forEach(([key, value]) => {
const target = document.querySelector(`[data-review="${key}"]`);
if (target) target.textContent = value || "—";
});
};
form.querySelectorAll("[data-next]").forEach((button) => {
button.addEventListener("click", () => {
if (currentStep === 1 && validateStepOne()) setStep(2);
else if (currentStep === 2 && validateStepTwo()) {
reviewValues();
setStep(3);
}
});
});
form.querySelectorAll("[data-back]").forEach((button) => {
button.addEventListener("click", () => setStep(Math.max(1, currentStep - 1)));
});
form.querySelectorAll("input[name='storePossession']").forEach((input) => {
input.addEventListener("change", updateAreaField);
});
areaInput?.addEventListener("input", updateAreaConversion);
form.addEventListener("input", (event) => {
const field = event.target;
if (!(field instanceof HTMLElement) || !field.id) return;
const errorId = `${field.id}-error`;
if (document.querySelector(`#${errorId}`)) clearFieldError(field.id, errorId);
});
form.addEventListener("change", (event) => {
const field = event.target;
if (!(field instanceof HTMLInputElement)) return;
if (field.type === "radio") {
const names = {
areaKind: "area-kind-error",
storePossession: "store-possession-error",
experience: "experience-error",
};
if (names[field.name]) clearRadioError(field.name, names[field.name]);
}
});
form.addEventListener("submit", (event) => {
event.preventDefault();
const consent = document.querySelector("#demo-consent");
const consentError = document.querySelector("#demo-consent-error");
if (consent instanceof HTMLInputElement) {
consent.removeAttribute("aria-invalid");
consent.removeAttribute("aria-describedby");
}
if (consentError) consentError.textContent = "";
if (!(consent instanceof HTMLInputElement) || !consent.checked) {
consent?.setAttribute("aria-invalid", "true");
consent?.setAttribute("aria-describedby", "demo-consent-error");
if (consentError) consentError.textContent = "문의 체험 안내를 확인해 주세요.";
consent?.focus();
return;
}
if (submitButton instanceof HTMLButtonElement) {
submitButton.disabled = true;
submitButton.textContent = "화면 상태 확인 중";
}
if (submitState) submitState.textContent = "";
window.setTimeout(() => {
if (fixture === "inquiry-fail") {
if (submitButton instanceof HTMLButtonElement) {
submitButton.disabled = false;
submitButton.textContent = "다시 시도하기";
}
if (submitState) {
submitState.textContent = "지금은 문의 준비 상태를 바꾸지 못했어요. 입력 내용은 화면에 그대로 두었습니다.";
}
return;
}
form.hidden = true;
if (indicatorList) indicatorList.hidden = true;
if (successPanel) {
successPanel.hidden = false;
successPanel.focus();
}
if (submitButton instanceof HTMLButtonElement) {
submitButton.disabled = false;
submitButton.textContent = "문의 준비 마치기";
}
}, 450);
});
document.querySelector("#review-again")?.addEventListener("click", () => {
if (successPanel) successPanel.hidden = true;
form.hidden = false;
if (indicatorList) indicatorList.hidden = false;
setStep(3);
});
document.querySelector("#restart-form")?.addEventListener("click", () => {
form.reset();
form.hidden = false;
if (successPanel) successPanel.hidden = true;
if (indicatorList) indicatorList.hidden = false;
if (submitState) submitState.textContent = "";
document.querySelectorAll(".field-error").forEach((error) => { error.textContent = ""; });
document.querySelectorAll(".has-error").forEach((field) => field.classList.remove("has-error"));
updateAreaField();
setStep(1);
});
updateAreaField();
setStep(1);
})();

View file

@ -0,0 +1,261 @@
(() => {
"use strict";
const fixture = new URLSearchParams(window.location.search).get("fixture") || "";
const formatWon = (value) => `${new Intl.NumberFormat("ko-KR").format(value)}원`;
const markBrokenImage = (image) => {
const shell = image.closest(".image-shell");
if (shell) shell.classList.add("is-broken");
};
document.querySelectorAll(".image-shell img").forEach((image) => {
image.addEventListener("error", () => markBrokenImage(image));
if (image.complete && image.naturalWidth === 0) markBrokenImage(image);
});
const menuButtons = [...document.querySelectorAll("[data-menu-filter]")];
const menuItems = [...document.querySelectorAll("[data-menu-category]")];
const menuEmpty = document.querySelector("#menu-empty");
const setMenuFilter = (filter) => {
const forceMenuEmpty = new URLSearchParams(window.location.search).get("fixture") === "menu-empty";
let visibleCount = 0;
menuButtons.forEach((button) => {
const active = button.dataset.menuFilter === filter;
button.classList.toggle("is-active", active);
button.setAttribute("aria-pressed", String(active));
});
menuItems.forEach((item) => {
const visible = !forceMenuEmpty && (filter === "all" || item.dataset.menuCategory === filter);
item.hidden = !visible;
if (visible) visibleCount += 1;
});
if (menuEmpty) menuEmpty.hidden = visibleCount > 0;
};
menuButtons.forEach((button) => {
button.addEventListener("click", () => setMenuFilter(button.dataset.menuFilter || "all"));
});
document.querySelector("[data-show-all-menu]")?.addEventListener("click", () => {
if (fixture === "menu-empty") {
const nextUrl = new URL(window.location.href);
nextUrl.searchParams.delete("fixture");
window.history.replaceState({}, "", nextUrl);
}
setMenuFilter("all");
});
if (fixture === "menu-empty") setMenuFilter("all");
const builder = document.querySelector("#burger-builder");
const builderSection = document.querySelector("#builder");
const setOptions = document.querySelector("#set-options");
const summaryHeading = document.querySelector("#summary-heading");
const summaryLines = document.querySelector("#summary-lines");
const summaryServing = document.querySelector("#summary-serving");
const summaryStore = document.querySelector("#summary-store");
const summaryStoreState = document.querySelector("#summary-store-state");
const summaryAllergens = document.querySelector("#summary-allergens");
const summaryTotal = document.querySelector("#summary-total");
const saveButton = document.querySelector("#save-builder");
const saveStatus = document.querySelector("#save-status");
const stores = {
seongsu: { label: "성수 예시점", state: "영업 중 예시", available: true },
yeonnam: { label: "연남 예시점", state: "오늘 영업 종료 예시", available: false },
mullae: { label: "문래 예시점", state: "오늘은 쉬어요", available: false },
};
const fieldValue = (name) => {
const field = builder?.elements.namedItem(name);
if (!field) return "";
if (field instanceof RadioNodeList) return field.value;
return field.value;
};
const setRadio = (name, value) => {
const radio = builder?.querySelector(`input[name="${name}"][value="${value}"]`);
if (radio instanceof HTMLInputElement) radio.checked = true;
};
const builderPrice = ({ flavor, patty, cheese, serving }) => {
const flavorBase = flavor === "spicy" ? 7400 : 6900;
const pattyAdd = Number(patty) === 2 ? 2000 : 0;
const cheeseAdd = Number(cheese) === 0 ? -400 : Number(cheese) === 2 ? 400 : 0;
const setAdd = serving === "set" ? 4100 : 0;
return flavorBase + pattyAdd + cheeseAdd + setAdd;
};
const updateBuilder = () => {
if (!builder) return;
const flavor = fieldValue("flavor");
const patty = fieldValue("patty");
const cheese = fieldValue("cheese");
const onion = fieldValue("onion");
const pickle = fieldValue("pickle");
const sauce = fieldValue("sauce");
const serving = fieldValue("serving");
const storeKey = fieldValue("store");
const store = stores[storeKey] || stores.seongsu;
const soldOut = fixture === "sold-out";
const setEnabled = serving === "set";
builder.querySelectorAll("select[name='side'], select[name='drink']").forEach((select) => {
select.disabled = !setEnabled;
});
setOptions?.classList.toggle("is-disabled", !setEnabled);
const names = {
onion: { none: "양파 없음", regular: "양파 기본", extra: "양파 많이" },
pickle: { none: "피클 없음", regular: "피클 기본", extra: "피클 많이" },
sauce: { none: "소스 없음", regular: "소스 기본", side: "소스 따로" },
};
const pattyLabel = Number(patty) === 2 ? "더블" : "싱글";
const flavorLabel = flavor === "spicy" ? "고추 피클" : "철판";
const cheeseLabel = Number(cheese) === 0 ? "치즈 없음" : `치즈 ${cheese === "2" ? "두" : "한"} 장`;
const productName = `${flavorLabel} ${pattyLabel}`;
const servingLabel = serving === "set" ? "세트 · 얇은 감자와 음료" : "단품";
if (summaryHeading) summaryHeading.textContent = productName;
if (summaryLines) {
summaryLines.textContent = `패티 ${patty === "2" ? "두" : "한"} 장 / ${cheeseLabel} / ${names.onion[onion]} / ${names.pickle[pickle]} / ${names.sauce[sauce]}`;
}
if (summaryServing) summaryServing.textContent = servingLabel;
if (summaryStore) summaryStore.textContent = store.label;
if (summaryStoreState) summaryStoreState.textContent = soldOut ? "오늘 품절 예시" : store.state;
const allergens = ["알", "대두", "밀", "쇠고기"];
if (Number(cheese) > 0) allergens.splice(1, 0, "우유");
if (flavor === "spicy") allergens.push("토마토");
if (summaryAllergens) summaryAllergens.textContent = allergens.join(", ");
if (summaryTotal) summaryTotal.textContent = formatWon(builderPrice({ flavor, patty, cheese, serving }));
if (saveButton) {
saveButton.disabled = soldOut || !store.available;
if (soldOut) saveButton.textContent = "오늘 품절 예시";
else if (!store.available) saveButton.textContent = "다른 매장을 골라 주세요";
else saveButton.textContent = "선택 내용 저장";
}
if (saveStatus) {
saveStatus.textContent = "";
saveStatus.classList.remove("is-error");
}
};
builder?.addEventListener("change", updateBuilder);
const applyPreset = (preset) => {
if (!builder) return;
if (preset === "single") {
setRadio("flavor", "classic");
setRadio("patty", "1");
} else if (preset === "spicy") {
setRadio("flavor", "spicy");
setRadio("patty", "2");
} else {
setRadio("flavor", "classic");
setRadio("patty", "2");
}
updateBuilder();
builderSection?.scrollIntoView({ behavior: "smooth", block: "start" });
window.setTimeout(() => builder.querySelector("input:checked")?.focus({ preventScroll: true }), 350);
};
document.querySelectorAll("[data-preset]").forEach((button) => {
button.addEventListener("click", () => applyPreset(button.dataset.preset || "double"));
});
saveButton?.addEventListener("click", () => {
if (!builder || !saveStatus) return;
const snapshot = Object.fromEntries(new FormData(builder).entries());
try {
if (fixture === "save-fail") throw new Error("fixture");
sessionStorage.setItem("griddle-club-demo-selection", JSON.stringify(snapshot));
saveStatus.classList.remove("is-error");
saveStatus.textContent = "이 기기의 현재 탭에 선택 내용을 남겼어요. 실제 주문으로 전송되지 않습니다.";
} catch {
saveStatus.classList.add("is-error");
saveStatus.textContent = "선택 내용을 남기지 못했어요. 고른 내용은 화면에 그대로 두었습니다.";
}
});
const storeCards = [...document.querySelectorAll("[data-store]")];
const mapPins = [...document.querySelectorAll("[data-map-store]")];
const storeSearch = document.querySelector("#store-search");
const clearStoreSearch = document.querySelector("#clear-store-search");
const storeEmpty = document.querySelector("#store-empty");
const regionButtons = [...document.querySelectorAll("[data-region]")];
let activeRegion = "all";
const selectStore = (storeKey, shouldMove = false) => {
const select = builder?.elements.namedItem("store");
if (select instanceof HTMLSelectElement) select.value = storeKey;
storeCards.forEach((card) => card.classList.toggle("is-selected", card.dataset.store === storeKey));
mapPins.forEach((pin) => pin.classList.toggle("is-active", pin.dataset.mapStore === storeKey));
updateBuilder();
if (shouldMove) builderSection?.scrollIntoView({ behavior: "smooth", block: "start" });
};
document.querySelectorAll("[data-select-store]").forEach((button) => {
button.addEventListener("click", () => selectStore(button.dataset.selectStore, true));
});
mapPins.forEach((button) => {
button.addEventListener("click", () => selectStore(button.dataset.mapStore, false));
});
const filterStores = () => {
const query = storeSearch instanceof HTMLInputElement ? storeSearch.value.trim().toLocaleLowerCase("ko-KR") : "";
let visibleCount = 0;
storeCards.forEach((card) => {
const regionMatch = activeRegion === "all" || card.dataset.region === activeRegion;
const queryMatch = !query || (card.dataset.search || "").toLocaleLowerCase("ko-KR").includes(query);
const visible = fixture !== "store-none" && regionMatch && queryMatch;
card.hidden = !visible;
if (visible) visibleCount += 1;
const pin = mapPins.find((item) => item.dataset.mapStore === card.dataset.store);
if (pin) pin.hidden = !visible;
});
if (storeEmpty) storeEmpty.hidden = visibleCount > 0;
if (clearStoreSearch) clearStoreSearch.hidden = !query;
};
storeSearch?.addEventListener("input", filterStores);
clearStoreSearch?.addEventListener("click", () => {
if (storeSearch instanceof HTMLInputElement) {
storeSearch.value = "";
storeSearch.focus();
}
filterStores();
});
regionButtons.forEach((button) => {
button.addEventListener("click", () => {
activeRegion = button.dataset.region || "all";
regionButtons.forEach((item) => {
const active = item === button;
item.classList.toggle("is-active", active);
item.setAttribute("aria-pressed", String(active));
});
filterStores();
});
});
document.querySelector("#reset-store-search")?.addEventListener("click", () => {
activeRegion = "all";
if (storeSearch instanceof HTMLInputElement) storeSearch.value = "";
regionButtons.forEach((button) => {
const active = button.dataset.region === "all";
button.classList.toggle("is-active", active);
button.setAttribute("aria-pressed", String(active));
});
filterStores();
storeSearch?.focus();
});
updateBuilder();
filterStores();
})();

View file

@ -0,0 +1,979 @@
.franchise-page {
background: var(--paper-bright);
}
.franchise-hero {
min-height: min(820px, calc(100svh - var(--header-height) - 30px));
display: grid;
grid-template-columns: minmax(360px, 0.92fr) minmax(0, 1.08fr);
background: var(--ink);
color: white;
}
.franchise-hero-copy {
align-self: center;
padding: clamp(76px, 10vw, 140px) var(--gutter);
padding-left: max(var(--gutter), calc((100vw - var(--wrap)) / 2));
}
.franchise-hero-copy .section-kicker {
color: #8ea5ff;
}
.franchise-hero h1 {
max-width: 700px;
margin: 22px 0 0;
font-size: clamp(48px, 5.6vw, 80px);
font-weight: 900;
letter-spacing: -0.085em;
line-height: 0.98;
}
.franchise-hero-copy > p:not(.section-kicker) {
max-width: 550px;
margin-top: 30px;
color: #c9c4bc;
font-size: clamp(16px, 1.5vw, 20px);
line-height: 1.75;
}
.light-link {
border-color: white;
}
.franchise-hero .hero-actions > a {
min-height: 48px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-top: 0;
line-height: 1.2;
}
.franchise-hero .hero-actions .light-link {
padding: 0 2px;
border-bottom: 0;
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 6px;
}
.franchise-truth {
max-width: 560px;
margin-top: 52px;
padding: 18px 20px;
border-left: 3px solid #8ea5ff;
background: #2b2926;
}
.franchise-truth strong {
font-size: 12px;
}
.franchise-truth p {
margin-top: 5px;
color: #aaa59d;
font-size: 11px;
}
.franchise-hero-media {
min-height: 680px;
overflow: hidden;
background: var(--steel);
}
.franchise-hero-media img {
width: 100%;
height: 100%;
object-fit: cover;
}
.franchise-hero-media figcaption {
position: absolute;
right: 16px;
bottom: 16px;
padding: 7px 10px;
background: var(--paper-bright);
color: var(--ink);
font-size: 10px;
font-weight: 700;
}
.fact-rail {
border-bottom: 1px solid var(--line);
background: var(--paper);
}
.fact-rail-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.fact-rail p {
min-height: 92px;
display: flex;
align-items: center;
gap: 16px;
padding: 18px 28px;
border-right: 1px solid var(--line);
font-size: 13px;
font-weight: 750;
}
.fact-rail p:first-child {
padding-left: 0;
}
.fact-rail p:last-child {
border-right: 0;
}
.fact-rail span,
.fit-number {
color: var(--blue);
font-family: var(--font-mono);
font-size: 11px;
}
.model-section {
background: var(--paper-bright);
}
.flow-board {
display: grid;
grid-template-columns: repeat(4, 1fr);
border: 1px solid var(--ink);
}
.flow-board article {
min-height: 340px;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: clamp(22px, 3vw, 34px);
border-right: 1px solid var(--ink);
}
.flow-board article:last-child {
border-right: 0;
background: var(--blue);
color: white;
}
.flow-board article > span {
margin-bottom: auto;
color: var(--blue);
font-family: var(--font-mono);
font-size: 11px;
}
.flow-board article:last-child > span {
color: #c8d3ff;
}
.flow-board h3 {
font-size: 23px;
letter-spacing: -0.045em;
}
.flow-board p {
margin-top: 8px;
color: var(--ink-soft);
font-size: 13px;
}
.flow-board article:last-child p {
color: #dce4ff;
}
.operator-fit {
display: grid;
grid-template-columns: 0.8fr 1.2fr;
gap: 70px;
margin-top: clamp(64px, 9vw, 110px);
padding-top: 42px;
border-top: 1px solid var(--ink);
}
.operator-fit h3 {
margin-top: 13px;
font-size: clamp(34px, 4.2vw, 54px);
letter-spacing: -0.065em;
line-height: 1.12;
}
.operator-fit ul {
margin: 0;
padding: 0;
list-style: none;
border-top: 1px solid var(--line);
}
.operator-fit li {
display: grid;
grid-template-columns: 130px 1fr;
gap: 20px;
padding: 19px 0;
border-bottom: 1px solid var(--line);
font-size: 13px;
}
.operator-fit span {
color: var(--ink-soft);
}
.site-fit-section {
background: var(--steel);
}
.fit-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
border-top: 1px solid var(--ink);
border-left: 1px solid var(--ink);
}
.fit-grid article {
min-height: 300px;
padding: clamp(22px, 3vw, 34px);
border-right: 1px solid var(--ink);
border-bottom: 1px solid var(--ink);
background: var(--paper-bright);
}
.fit-grid .fit-main {
grid-column: span 2;
grid-row: span 2;
min-height: 600px;
display: flex;
flex-direction: column;
background: var(--ink);
color: white;
}
.fit-grid h3 {
margin-top: 46px;
font-size: clamp(28px, 3.5vw, 48px);
letter-spacing: -0.06em;
}
.fit-grid article > p:not(.fit-number) {
margin-top: 14px;
color: var(--ink-soft);
font-size: 13px;
}
.fit-grid .fit-main > p:not(.fit-number) {
max-width: 490px;
color: #d4cec5;
font-size: 16px;
}
.fit-main .fit-number {
color: #9db0ff;
}
.fit-main ul {
margin: auto 0 0;
padding: 24px 0 0;
border-top: 1px solid #625f5a;
list-style: none;
}
.fit-main li {
padding: 8px 0;
color: #e8e3db;
font-size: 13px;
}
.franchise-process {
background: var(--paper-bright);
}
.franchise-timeline {
margin: 0;
padding: 0;
list-style: none;
border-top: 1px solid var(--ink);
}
.franchise-timeline li {
display: grid;
grid-template-columns: 56px 1fr auto;
align-items: center;
gap: 28px;
min-height: 124px;
padding: 22px 0;
border-bottom: 1px solid var(--line);
}
.franchise-timeline li > span {
color: var(--blue);
font-family: var(--font-mono);
font-size: 12px;
}
.franchise-timeline h3 {
font-size: 24px;
letter-spacing: -0.045em;
}
.franchise-timeline p {
margin-top: 3px;
color: var(--ink-soft);
font-size: 13px;
}
.franchise-timeline small {
padding: 6px 9px;
border: 1px solid var(--line);
font-size: 10px;
font-weight: 700;
}
.cost-section {
background: var(--ink);
color: white;
}
.cost-section .section-kicker {
color: #8ea5ff;
}
.cost-head {
display: grid;
grid-template-columns: 1.25fr 0.75fr;
align-items: end;
gap: 50px;
margin-bottom: 64px;
}
.cost-head h2 {
margin-top: 14px;
}
.unknown-cost {
padding: 22px;
border: 1px solid #625f5a;
background: #2a2825;
}
.unknown-cost span {
color: #aaa59d;
font-size: 10px;
}
.unknown-cost strong {
display: block;
margin-top: 3px;
color: #8ea5ff;
font-size: 25px;
}
.unknown-cost p {
margin-top: 7px;
color: #bdb7af;
font-size: 12px;
}
.cost-table-wrap {
overflow-x: auto;
border-top: 1px solid #827d75;
}
.cost-table {
width: 100%;
min-width: 780px;
border-collapse: collapse;
font-size: 12px;
}
.cost-table th,
.cost-table td {
padding: 17px 14px;
border-bottom: 1px solid #4f4c47;
text-align: left;
}
.cost-table thead th {
color: #9f9a92;
font-size: 10px;
}
.cost-table tbody th {
color: white;
font-weight: 750;
}
.cost-table td {
color: #c4beb6;
}
.cost-table td:nth-child(3) {
color: #8ea5ff;
font-family: var(--font-mono);
font-weight: 600;
}
.cost-table tfoot th,
.cost-table tfoot td {
border-bottom: 0;
background: var(--blue);
color: white;
font-weight: 750;
}
.cost-warning {
margin-top: 18px;
color: #aaa59d;
font-size: 11px;
}
.inquiry-section {
background: var(--paper);
}
.inquiry-layout {
display: grid;
grid-template-columns: minmax(280px, 0.7fr) minmax(480px, 1.3fr);
gap: clamp(44px, 8vw, 110px);
align-items: start;
}
.inquiry-intro {
position: sticky;
top: calc(var(--header-height) + 32px);
}
.inquiry-intro h2 {
margin-top: 14px;
}
.inquiry-intro > p:not(.section-kicker) {
max-width: 460px;
margin-top: 26px;
color: var(--ink-soft);
font-size: 15px;
}
.privacy-note {
margin-top: 32px;
padding: 18px;
border-left: 3px solid var(--danger);
background: #fff0eb;
}
.privacy-note strong {
font-size: 12px;
}
.privacy-note p {
margin-top: 5px;
color: #733c33;
font-size: 11px;
}
.inquiry-panel {
border: 1px solid var(--line-strong);
background: var(--paper-bright);
box-shadow: var(--shadow);
}
.step-indicator {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin: 0;
padding: 0;
border-bottom: 1px solid var(--line);
list-style: none;
}
.step-indicator li {
min-height: 76px;
display: flex;
align-items: center;
gap: 10px;
padding: 12px 18px;
border-right: 1px solid var(--line);
color: var(--ink-faint);
}
.step-indicator li:last-child {
border-right: 0;
}
.step-indicator li.is-current {
background: var(--blue);
color: white;
}
.step-indicator span {
font-family: var(--font-mono);
font-size: 12px;
}
.step-indicator small {
font-size: 11px;
font-weight: 750;
}
.form-step,
.inquiry-success {
padding: clamp(26px, 5vw, 50px);
}
.is-js .form-step:not(.is-current) {
display: none;
}
.step-count {
color: var(--blue);
font-family: var(--font-mono);
font-size: 11px;
}
.form-step h3,
.inquiry-success h3 {
margin-top: 8px;
font-size: clamp(29px, 4vw, 43px);
font-weight: 850;
letter-spacing: -0.06em;
line-height: 1.2;
}
.form-field {
margin-top: 30px;
}
.form-field > label,
.form-field > legend {
display: block;
margin-bottom: 9px;
font-size: 13px;
font-weight: 750;
}
.form-field input[type="text"],
.form-field input[type="number"],
.form-field select {
width: 100%;
min-height: 52px;
padding: 11px 13px;
border: 1px solid var(--line-strong);
border-radius: var(--radius-sm);
background: white;
}
.form-field > small,
.form-field output {
display: block;
margin-top: 7px;
color: var(--ink-faint);
font-size: 10px;
}
.field-error {
min-height: 20px;
margin-top: 5px;
color: var(--danger);
font-size: 11px;
font-weight: 700;
}
.form-field.has-error input,
.form-field.has-error select,
.form-field.has-error .radio-grid {
border-color: var(--danger);
}
.radio-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.radio-grid-two {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.radio-grid label {
position: relative;
}
.radio-grid input {
position: absolute;
z-index: 1;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.radio-grid span {
min-height: 48px;
display: grid;
place-items: center;
padding: 9px;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
font-size: 12px;
font-weight: 700;
text-align: center;
}
.radio-grid input:checked + span {
border-color: var(--blue);
background: var(--blue);
color: white;
}
.radio-grid input:focus-visible + span {
outline: 3px solid var(--focus);
outline-offset: 3px;
}
.unit-input {
display: flex;
align-items: center;
border: 1px solid var(--line-strong);
background: white;
}
.unit-input input {
border: 0 !important;
}
.unit-input span {
padding-right: 14px;
font-size: 12px;
font-weight: 750;
}
.form-actions {
display: flex;
justify-content: flex-end;
margin-top: 36px;
}
.split-actions {
justify-content: space-between;
gap: 12px;
}
.form-actions .button-ghost {
margin-top: 0;
background: white;
}
.review-list {
margin-top: 30px;
border-top: 1px solid var(--ink);
}
.review-list div {
display: grid;
grid-template-columns: 130px 1fr;
gap: 20px;
padding: 14px 0;
border-bottom: 1px solid var(--line);
font-size: 12px;
}
.review-list dt {
color: var(--ink-faint);
}
.review-list dd {
font-weight: 750;
}
.consent-check {
position: relative;
min-height: 44px;
display: grid;
grid-template-columns: 20px 1fr;
align-items: start;
gap: 10px;
margin-top: 24px;
font-size: 12px;
font-weight: 650;
}
.consent-check input {
position: absolute;
z-index: 1;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.consent-check span {
position: relative;
grid-column: 2;
}
.consent-check span::before {
content: "";
position: absolute;
top: 2px;
left: -30px;
width: 18px;
height: 18px;
border: 1px solid var(--line-strong);
background: white;
}
.consent-check input:checked + span::before {
border-color: var(--blue);
background: var(--blue);
box-shadow: inset 0 0 0 4px white;
}
.consent-check input:focus-visible + span {
outline: 3px solid var(--focus);
outline-offset: 5px;
}
.submit-state {
min-height: 24px;
margin-top: 12px;
color: var(--danger);
font-size: 11px;
font-weight: 700;
}
.inquiry-success {
min-height: 580px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
.success-mark {
width: 58px;
height: 58px;
display: grid;
place-items: center;
margin-bottom: 26px;
background: var(--blue);
color: white;
font-size: 28px;
}
.inquiry-success > p:not(.section-kicker) {
max-width: 460px;
margin-top: 15px;
color: var(--ink-soft);
font-size: 14px;
}
.success-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 30px;
}
.success-actions .button {
margin-top: 0;
}
.franchise-close {
padding-block: var(--section);
background: var(--blue);
color: white;
}
.close-row {
display: grid;
grid-template-columns: 1fr auto;
align-items: end;
gap: 44px;
}
.franchise-close .section-kicker {
color: #c8d3ff;
}
.franchise-close h2 {
max-width: 850px;
margin-top: 14px;
font-size: clamp(42px, 6vw, 78px);
letter-spacing: -0.075em;
line-height: 1.06;
}
.franchise-foot-grid {
grid-template-columns: 1.2fr 0.8fr 1.4fr;
}
@media (max-width: 960px) {
.franchise-hero {
grid-template-columns: 0.95fr 1.05fr;
}
.franchise-hero-copy {
padding-left: var(--gutter);
}
.flow-board {
grid-template-columns: repeat(2, 1fr);
}
.flow-board article:nth-child(2) {
border-right: 0;
}
.flow-board article:nth-child(-n + 2) {
border-bottom: 1px solid var(--ink);
}
.fit-grid {
grid-template-columns: repeat(2, 1fr);
}
.cost-head,
.inquiry-layout {
grid-template-columns: 1fr;
}
.inquiry-intro {
position: static;
}
.franchise-foot-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 760px) {
.franchise-hero {
grid-template-columns: 1fr;
}
.franchise-hero-copy {
padding: 72px var(--gutter) 52px;
}
.franchise-hero h1 {
font-size: clamp(40px, 13.5vw, 60px);
}
.franchise-hero-media {
min-height: 560px;
}
.fact-rail-grid {
grid-template-columns: 1fr;
}
.fact-rail p,
.fact-rail p:first-child {
min-height: 64px;
padding: 14px 0;
border-right: 0;
border-bottom: 1px solid var(--line);
}
.operator-fit {
grid-template-columns: 1fr;
gap: 34px;
}
.fit-grid .fit-main {
min-height: 520px;
}
.franchise-timeline li {
grid-template-columns: 42px 1fr;
}
.franchise-timeline small {
grid-column: 2;
justify-self: start;
}
.close-row {
grid-template-columns: 1fr;
align-items: start;
}
.franchise-foot-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 540px) {
.franchise-hero-media {
min-height: 460px;
}
.flow-board,
.fit-grid {
display: block;
border-left: 1px solid var(--ink);
}
.flow-board article,
.flow-board article:nth-child(2),
.fit-grid article {
border-right: 1px solid var(--ink);
border-bottom: 1px solid var(--ink);
}
.flow-board article,
.flow-board article:nth-child(2) {
min-height: 170px;
justify-content: flex-start;
}
.flow-board article > span {
margin-bottom: 30px;
}
.fit-grid .fit-main {
min-height: 500px;
}
.operator-fit li {
grid-template-columns: 1fr;
gap: 4px;
}
.step-indicator li {
min-height: 64px;
justify-content: center;
padding: 9px;
}
.step-indicator small {
display: inline;
font-size: 10px;
}
.radio-grid {
grid-template-columns: repeat(2, 1fr);
}
.form-actions,
.split-actions,
.success-actions {
flex-direction: column-reverse;
}
.form-actions .button,
.success-actions .button {
width: 100%;
}
.review-list div {
grid-template-columns: 1fr;
gap: 3px;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
:root {
color-scheme: light;
--paper: #f4f1e9;
--paper-bright: #fffdf8;
--ink: #211f1c;
--ink-soft: #56514a;
--ink-faint: #625d56;
--line: #c9c2b7;
--line-strong: #8a8379;
--blue: #2147e7;
--blue-dark: #1533ac;
--blue-pale: #dce4ff;
--steel: #d9dcda;
--cheese: #ffc642;
--danger: #b33124;
--success: #17683b;
--focus: #2147e7;
--font-sans: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-mono: "IBM Plex Mono", "SFMono-Regular", Consolas, monospace;
--wrap: 1180px;
--gutter: clamp(18px, 4vw, 52px);
--section: clamp(76px, 10vw, 144px);
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 24px;
--space-6: 32px;
--space-7: 48px;
--space-8: 72px;
--radius-sm: 4px;
--radius: 10px;
--radius-lg: 18px;
--shadow: 0 18px 50px rgb(33 31 28 / 10%);
--header-height: 78px;
}
@media (max-width: 720px) {
:root {
--header-height: 64px;
--section: 72px;
}
}