mobile(supporters): 회복 일정 플래너 반응형 정비 + 사이트 헤더 모바일·태블릿 대응

- 헤더(≤900px): 첫 줄 브랜드·상담 예약, 둘째 줄 메뉴 가로 스크롤. 메뉴 6개가 두 줄로 겹치던 문제를 DOM 변경 없이 CSS만으로 해결(구조 지문 유지)
- 플래너 모바일: 히어로 통계·핵심 날짜 2열, 시술 그리드 2열, 타임라인 날짜 머리 가로 배치, 숙소·예약 카드 1열, 입력 16px(iOS 확대 방지)
- 하단 고정 요약 바: 결과가 있고 결과 섹션이 화면 아래에 있을 때만 표시. 시술명, 입국→출국, 박 수, 일정표 보기 버튼
- 검증: 1280 창 안 390/768px 아이프레임 하네스로 확인 (헤드리스 크롬은 창 최소 폭 때문에 400px 직접 캡처가 부정확)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Haewon Kam 2026-09-10 16:20:39 +09:00
parent 27e77add65
commit 378ca7e7cd
4 changed files with 74 additions and 4 deletions

View File

@ -36,6 +36,7 @@ const nHotels = PL.places.filter((x) => x.category === 'hotel').length;
<section class="section tint"><div class="wrap-wide"><noscript><p class="notice">{lang === 'ko' ? '이 일정표는 브라우저에서 계산합니다. 자바스크립트를 켜 주세요.' : 'This planner runs in your browser. Please enable JavaScript.'}</p></noscript></div></section>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<div class="sticky-bar" id="sticky" hidden><div class="s"><b id="sticky-title"></b><small id="sticky-sub"></small></div><button type="button" data-action="go">{t.stickyView}</button></div>
<script>
import P from '../data/procedures.json';
@ -54,6 +55,7 @@ const nHotels = PL.places.filter((x) => x.category === 'hotel').length;
const pageLang = (root.dataset.lang === 'en' ? 'en' : 'ko') as Lang;
const pageUrl = (l: Lang) => (l === 'en' ? '/en/plan' : '/plan');
const toastEl = document.getElementById('toast')!;
const sticky = document.getElementById('sticky')!;
const reservationUrl: string = (factSheet as any).reservationUrl || (factSheet as any).url || '/visit';
interface State { lang: Lang; nat: string; proc: string; date: string; prefs: string[] }
@ -244,7 +246,20 @@ const nHotels = PL.places.filter((x) => x.category === 'hotel').length;
}
root.innerHTML = renderInput() + result;
syncHash();
updateSticky();
}
/** 모바일 하단 요약 바: 결과가 있고 결과 섹션이 아직 화면 아래에 있을 때만 보인다. */
function updateSticky() {
const p = currentProc();
if (!lastPlan || !p) { sticky.hidden = true; return; }
const t = T();
const short = (iso: string) => iso.slice(5).replace('-', '.');
(document.getElementById('sticky-title') as HTMLElement).textContent = (p.label as any)[state.lang];
(document.getElementById('sticky-sub') as HTMLElement).textContent = fill(t.stickyStay, { a: short(lastPlan.arriveBy), b: lastPlan.earliestDeparture ? short(lastPlan.earliestDeparture) : t.pending, n: lastPlan.stayNights ?? '?' });
const r = document.getElementById('result');
sticky.hidden = !r || r.getBoundingClientRect().top < window.innerHeight * 0.6;
}
window.addEventListener('scroll', () => { if (lastPlan) updateSticky(); }, { passive: true });
// ---------- 이벤트 ----------
document.addEventListener('click', (e) => {

View File

@ -102,7 +102,9 @@
"sub_book": "입국 권장일부터 출국 가능 최소일까지 날짜를 채워 검색합니다.",
"sub_rules": "담당 원장 안내가 우선이고, 아래는 병원 공개 안내 기준입니다.",
"empty_title": "시술과 수술일을 고르면 여기에 일정표가 나옵니다",
"empty_sub": "수술일은 예상일이어도 됩니다."
"empty_sub": "수술일은 예상일이어도 됩니다.",
"stickyView": "일정표 보기",
"stickyStay": "{a} → {b} · {n}박"
},
"en": {
"eyebrow": "Recovery Planner",
@ -206,6 +208,8 @@
"sub_book": "Searches are filled with your arrival and earliest-departure dates.",
"sub_rules": "Your surgeon's instructions come first. Below is the clinic's published guidance.",
"empty_title": "Pick a procedure and a surgery date to see your schedule here",
"empty_sub": "An estimated date is fine."
"empty_sub": "An estimated date is fine.",
"stickyView": "See schedule",
"stickyStay": "{a} → {b} · {n} nights"
}
}

View File

@ -264,3 +264,15 @@ td { color: var(--slate-700); }
.totop.show { opacity: 1; transform: none; pointer-events: auto; }
.totop:hover { box-shadow: 0 10px 28px rgba(2,19,65,0.40); }
@media (max-width: 720px) { .totop { right: 1rem; bottom: 1.1rem; width: 44px; height: 44px; } }
/* 태블릿·모바일 헤더 (≤900px): 첫 줄 브랜드 + 상담 예약, 둘째 줄 메뉴 가로 스크롤. 메뉴 6개가 두 줄로 겹치던 문제를 DOM 변경 없이 CSS만으로 해결한다 (2026-09-10) */
@media (max-width: 900px) {
.site-header .bar { height: auto; flex-wrap: wrap; padding-top: 0.6rem; padding-bottom: 0; row-gap: 0.1rem; }
.site-header .brand { flex: 1; }
.site-header .right { display: contents; }
.site-header .cta { order: 2; }
.site-header .nav { order: 3; display: flex; gap: 1rem; overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch; scrollbar-width: none; padding: 0.35rem 0 0.7rem; width: 100%; }
.site-header .nav::-webkit-scrollbar { display: none; }
.site-header .nav a { margin-left: 0; flex: none; font-size: 0.9rem; }
}

View File

@ -13,7 +13,7 @@
.lang-links a.on { background: linear-gradient(to right, var(--grad-start), var(--grad-end)); border-color: transparent; }
.lang-links a.on .ico { background: rgba(255,255,255,0.2); color: #fff; }
.lang-links a:hover { text-decoration: none; border-color: rgba(216,180,254,0.6); }
.plan-hero .stats { margin-top: 1.8rem; }
.plan-hero .stats { margin-top: 1.8rem; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.plan-hero .stat b { font-size: 2.2rem; }
/* 입력 단계 카드 (틴트 섹션) */
@ -122,8 +122,47 @@ button.btn { font: inherit; cursor: pointer; border: 0; }
.plan-entry p { margin: 0; color: var(--slate-700); font-size: 0.95rem; }
.plan-entry .btn { justify-self: start; margin-top: 0.4rem; }
/* 태블릿 (≤900px): 히어로 통계 2열 */
@media (max-width: 900px) { .plan-hero .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* 모바일 (≤720px): 통계·날짜 카드 2열, 타임라인 날짜 머리 가로 배치, 히어로 여백 축소 */
@media (max-width: 720px) {
.plan-hero { padding: 2.4rem 0 2.2rem; }
.plan-hero h1 { font-size: 1.9rem; }
.plan-hero .lede { font-size: 0.95rem; }
.plan-hero .stats, .stats.dates { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.7rem; }
.plan-hero .stat, .stats.dates .stat { padding: 0.95rem 1rem; }
.plan-hero .stat b { font-size: 1.8rem; }
.plan-hero .stat .label { font-size: 0.9rem; }
.plan-hero .stat .desc { font-size: 0.78rem; }
.stats.dates .stat b { font-size: 1.35rem; }
.stats.dates .stat .desc { font-size: 0.78rem; }
.section.tint.plan-input { padding: 2.4rem 0 !important; }
.sec-head h2 { font-size: 1.6rem; }
.card { padding: 1.15rem 1.2rem; }
.field select, .field input[type="date"] { max-width: none; width: 100%; font-size: 16px; }
.proc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.day { padding: 0.85rem 1rem; gap: 0.5rem; }
.day .d { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.2rem 0.6rem; }
.day .picks a, .day .picks span.pick { font-size: 0.82rem; padding: 0.3rem 0.65rem; }
.book-row { grid-template-columns: 1fr; }
.hotel-grid { grid-template-columns: 1fr; }
.plan-result-pad { padding-bottom: 4.5rem; }
}
/* 하단 고정 요약 바 (모바일에서 결과가 있을 때만) */
.sticky-bar { display: none; }
@media (max-width: 720px) {
.sticky-bar { position: fixed; left: 0.8rem; right: 4.2rem; bottom: 0.9rem; z-index: 15; display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; background: var(--primary-900); color: #fff; border-radius: 999px; padding: 0.55rem 0.6rem 0.55rem 1rem; box-shadow: 0 10px 28px rgba(2,19,65,0.35); transition: opacity 0.2s, transform 0.2s; }
.sticky-bar[hidden] { display: none; }
.sticky-bar .s { display: grid; line-height: 1.2; min-width: 0; }
.sticky-bar .s b { font-family: "Playfair Display", Georgia, serif; font-size: 0.95rem; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sticky-bar .s small { font-size: 0.7rem; color: var(--purple-200); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sticky-bar button { flex: none; border: 0; border-radius: 999px; background: linear-gradient(to right, var(--grad-start), #6C5CE7); color: #fff; font: inherit; font-size: 0.82rem; font-weight: 700; padding: 0.5rem 0.9rem; cursor: pointer; }
}
@media print {
.site-header, .backbar-wrap, .totop, .site-footer, .sample-banner, .plan-input, .plan-actions, .toast, .lang-links { display: none !important; }
.site-header, .backbar-wrap, .totop, .site-footer, .sample-banner, .plan-input, .plan-actions, .toast, .lang-links, .sticky-bar { display: none !important; }
.section { padding: 1rem 0 !important; }
.section.dark, .section.tint { background: #fff !important; color: var(--primary-900); }
.section.dark .sec-head h2, .plan-hero h1 { -webkit-text-fill-color: var(--primary-900); background: none; }