o2o-site-AEO/solution/site/src/layouts/minimal/Hero.tsx
Mina Choi 7a7f00dd15 [fix] solution/site: 첫 화면 태그 삭제·소문구 공통화 — 부티크 모바일도 문 모양
- 라운드·코랄·미니멀·솔숲: 히어로의 ‘주차 가능’ 칩 삭제. 일러스트: 지역 말풍선 삭제
- kit/heroSub.ts: 소문구 = 사장님 문구, 없으면 가장 가까운 명소까지 거리. 솔숲에만 있던 규칙을 9종 공통으로
- 고택·부티크: 캐치프레이즈 아래로 소문구 순서 변경
- 부티크 모바일: 전체화면 사진(664px)을 데스크톱과 같은 아치로 — 첫 화면 593px
- docs/TEMPLATE_DESIGN.md: 첫 화면 규칙

tsc·eslint 통과 · vitest 123 passed · 9종 모바일·1440px 첫 화면 캡처 확인

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:33:17 +09:00

88 lines
2.9 KiB
TypeScript

import {PlaceCategory} from '@o2o/shared';
import {HeroCatchphrase} from '@site/sections/HeroCatchphrase';
import {useSite} from '@site/lib/site-context';
import {bookingActionLabel, bookingLinks, isSectionEnabled, stayBookingView} from '@site/lib/derive';
import {heroSub} from '../kit/heroSub';
import {Car} from '../paper/Car';
import {BOOK_EVENT} from '../paper/Rooms';
import {Weather} from './Weather';
const MAX_SLIDES = 5;
export function Hero() {
const payload = useSite();
const {place, narrative} = payload;
const slides = [...payload.media]
.filter((image) => image.alt?.trim())
.sort((a, b) => Number(b.isPrimary) - Number(a.isPrimary))
.slice(0, MAX_SLIDES);
const fixed = narrative.tagline ?? narrative.heroSubline;
const lodging = place.category === PlaceCategory.LODGING;
const sub = heroSub(payload);
const region = place.addressLocality ?? place.addressRegion;
const booking = bookingLinks(payload)[0];
const stay = lodging ? stayBookingView(payload) : null;
return (
<>
<div className="hero" id="top" data-pg="home">
<div className="hero-card">
{slides.length > 0 && (
<Car
label={`${place.name} 사진`}
autoplay={5000}
dots
style={{height: '100%'}}
slides={slides.map((image, i) => (
<img
key={image.mediaId}
src={image.url}
alt={image.alt}
fetchPriority={i === 0 ? 'high' : 'low'}
loading={i === 0 ? undefined : 'lazy'}
decoding="async"
/>
))}
/>
)}
<div className="hero-copy">
<h1 className="eyebrow">
{place.name}
{region && ` · ${region}`}
</h1>
{lodging ? (
<p className="hl">
<HeroCatchphrase />
</p>
) : (
fixed && <p className="hl in">{fixed}</p>
)}
{lodging && sub && <p className="tagline">{sub}</p>}
<div className="hero-cta">
{stay ? (
<button type="button" className="btn" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
예약하기
</button>
) : (
booking && (
<a className="btn" href={booking.url} target="_blank" rel="noopener noreferrer">
{bookingActionLabel(booking)}
</a>
)
)}
<a className="btn soft" href="#/stay">
이용안내 보기
</a>
</div>
</div>
</div>
</div>
{isSectionEnabled(payload, 'weather') && (
<div data-editor-id="weather" style={{display: 'contents'}}>
<Weather />
</div>
)}
</>
);
}