[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>
This commit is contained in:
parent
865eb0cc77
commit
7a7f00dd15
@ -85,6 +85,10 @@
|
||||
|
||||
## 6. 값 보여주기
|
||||
|
||||
- **첫 화면은 캐치프레이즈 → 그 아래 소문구.** 소문구는 사장님 문구, 없으면 가장 가까운 명소까지 거리(`kit/heroSub.ts`)
|
||||
- 첫 화면에 태그·칩(‘주차 가능’, 지역 말풍선)을 두지 않는다
|
||||
- 부티크 첫 화면은 모바일도 문(아치) 모양이다. 전체화면 사진으로 되돌리지 않는다
|
||||
|
||||
- **‘가능 · 있음’은 ✓, ‘불가 · 없음’은 ✕ 목록으로 쓴다.** `주차 / 가능` 같은 표로 쓰지 않는다
|
||||
- 마크업: `<ul class="amen"><li class="on"><i class="ic">✓</i><span><b>주차</b> 가능</span></li>`
|
||||
- 색: ✓ `#15803d`, ✕ `#c2410c`. 가능한 것을 먼저 놓는다
|
||||
|
||||
@ -2,6 +2,7 @@ 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 {BOOK_EVENT} from '../paper/Rooms';
|
||||
import {Weather} from './Weather';
|
||||
|
||||
@ -12,6 +13,7 @@ export function Hero() {
|
||||
.filter((image) => image.alt?.trim())
|
||||
.sort((a, b) => Number(b.isPrimary) - Number(a.isPrimary));
|
||||
const fixed = narrative.tagline ?? narrative.heroSubline;
|
||||
const sub = heroSub(payload);
|
||||
const lodging = place.category === PlaceCategory.LODGING;
|
||||
const stay = lodging ? stayBookingView(payload) : null;
|
||||
const link = bookingLinks(payload)[0];
|
||||
@ -29,7 +31,7 @@ export function Hero() {
|
||||
<span className="no" /> 오늘의 문장
|
||||
</p>
|
||||
{(lodging || fixed) && <p className="rot">{lodging ? <HeroCatchphrase /> : fixed}</p>}
|
||||
{lodging && fixed && <p className="m sub">{fixed}</p>}
|
||||
{lodging && sub && <p className="m sub">{sub}</p>}
|
||||
<div className="hx">
|
||||
{stay ? (
|
||||
<button type="button" className="line line--ink" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
|
||||
|
||||
@ -3,6 +3,7 @@ import {HeroCatchphrase} from '@site/sections/HeroCatchphrase';
|
||||
import {useSite} from '@site/lib/site-context';
|
||||
import {bookingActionLabel, bookingLinks, isSectionEnabled, stayBookingView} from '@site/lib/derive';
|
||||
import {PlaceCategory} from '@o2o/shared';
|
||||
import {heroSub} from '../kit/heroSub';
|
||||
import {BOOK_EVENT} from '../paper/Rooms';
|
||||
import {pad2} from './ui';
|
||||
import {Weather} from './Weather';
|
||||
@ -26,7 +27,7 @@ export function Hero() {
|
||||
}, 5000);
|
||||
return () => window.clearInterval(timer);
|
||||
}, [total]);
|
||||
const fixed = narrative.tagline ?? narrative.heroSubline;
|
||||
const sub = heroSub(payload);
|
||||
const english = place.englishName?.trim();
|
||||
const locality = [place.addressLocality, place.addressRegion].find(Boolean);
|
||||
const stayBooking = place.category === PlaceCategory.LODGING ? stayBookingView(payload) : null;
|
||||
@ -57,10 +58,10 @@ export function Hero() {
|
||||
{english && <p className="b-hero-en">{english}</p>}
|
||||
<h1 className={english ? 'b-hero-name' : 'b-hero-name b-hero-name--solo'}>{place.name}</h1>
|
||||
{locality && <p className="b-hero-loc">{locality}</p>}
|
||||
{fixed && <p className="b-hero-ko">{fixed}</p>}
|
||||
<p className="b-hero-cp">
|
||||
<HeroCatchphrase />
|
||||
</p>
|
||||
{sub && <p className="b-hero-ko">{sub}</p>}
|
||||
<div className="b-hero-act">
|
||||
{stayBooking ? (
|
||||
<button type="button" className="btn" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
|
||||
|
||||
@ -351,3 +351,16 @@
|
||||
.w4-boutique #around .guide--card{grid-template-columns:repeat(3,minmax(0,1fr));gap:20px 10px}
|
||||
.w4-boutique #around .kcard-t{font-size:15px}
|
||||
@media (min-width:1024px){.w4-boutique .rail>.kcard{flex-basis:calc((100% - 48px) / 3)}.w4-boutique .guide--card{grid-template-columns:repeat(3,minmax(0,1fr));gap:36px 24px}}
|
||||
@media (max-width:1023px){
|
||||
.w4-boutique{--aw:min(250px,calc(100vw - 88px));--ah:calc(var(--aw) * 1.12)}
|
||||
.w4-boutique[data-page=home] .b-hd:not(.solid){color:var(--brown)}
|
||||
.w4-boutique .b-hero{height:auto;min-height:0;padding:calc(84px + var(--ah) + 24px) var(--g) 8px;background:transparent;overflow:visible}
|
||||
.w4-boutique .b-hero:before{content:"";position:absolute;top:100px;left:50%;width:var(--aw);height:var(--ah);margin-left:calc(var(--aw) / -2 + 14px);border-radius:999px 999px 0 0;background:var(--bg2)}
|
||||
.w4-boutique .b-hero:after{display:none}
|
||||
.w4-boutique .b-slide{inset:84px auto auto 50%;width:var(--aw);height:var(--ah);margin-left:calc(var(--aw) / -2);border-radius:999px 999px 0 0;overflow:hidden;z-index:1}
|
||||
.w4-boutique .b-hero-copy{position:relative;left:auto;right:auto;bottom:auto;padding:0;color:var(--brown)}
|
||||
.w4-boutique .b-hero-ko{color:var(--ink)}
|
||||
.w4-boutique .b-hero-count{top:calc(84px + var(--ah) - 34px);bottom:auto}
|
||||
.w4-boutique .b-hero--plain{padding-top:120px}
|
||||
.w4-boutique .b-hero--plain:before{display:none}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import {HeroCatchphrase} from '@site/sections/HeroCatchphrase';
|
||||
import {useSite} from '@site/lib/site-context';
|
||||
import {galleryImages, isSectionEnabled} from '@site/lib/derive';
|
||||
import {useLiveWeather} from '@site/lib/use-live-weather';
|
||||
import {heroSub} from '../kit/heroSub';
|
||||
import {Car} from '../paper/Car';
|
||||
import {MoodIcon, Weather} from './Weather';
|
||||
|
||||
@ -18,7 +19,7 @@ export function Hero() {
|
||||
const fixed = narrative.tagline ?? narrative.heroSubline;
|
||||
const lodging = place.category === PlaceCategory.LODGING;
|
||||
const where = place.addressLocality ?? place.addressRegion;
|
||||
const sub = [where, fixed].filter(Boolean).join(' · ');
|
||||
const sub = [where, heroSub(payload)].filter(Boolean).join(' · ');
|
||||
const showWeather = isSectionEnabled(payload, 'weather');
|
||||
const weather = useLiveWeather({
|
||||
initial: payload.local.weather,
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import {PlaceCategory} from '@o2o/shared';
|
||||
import {HeroCatchphrase} from '@site/sections/HeroCatchphrase';
|
||||
import {useSite} from '@site/lib/site-context';
|
||||
import {bookingActionLabel, bookingLinks, heroFacts, isSectionEnabled, stayBookingView} from '@site/lib/derive';
|
||||
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';
|
||||
@ -17,10 +18,10 @@ export function Hero() {
|
||||
.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;
|
||||
const chips = heroFacts(payload).map((row) => `${row.label} ${row.value}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -56,16 +57,7 @@ export function Hero() {
|
||||
) : (
|
||||
fixed && <p className="hl in">{fixed}</p>
|
||||
)}
|
||||
{lodging && fixed && <p className="tagline">{fixed}</p>}
|
||||
{chips.length > 0 && (
|
||||
<div className="chips">
|
||||
{chips.map((chip) => (
|
||||
<span key={chip} className="chip">
|
||||
{chip}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{lodging && sub && <p className="tagline">{sub}</p>}
|
||||
<div className="hero-cta">
|
||||
{stay ? (
|
||||
<button type="button" className="btn" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
|
||||
|
||||
@ -4,6 +4,7 @@ import {useSite} from '@site/lib/site-context';
|
||||
import {useLiveWeather} from '@site/lib/use-live-weather';
|
||||
import {useWeatherNotes} from '@site/lib/use-weather-note';
|
||||
import {bookingActionLabel, bookingLinks, isSectionEnabled, stayBookingView, weatherBand, weatherMood} from '@site/lib/derive';
|
||||
import {heroSub} from '../kit/heroSub';
|
||||
import {BOOK_EVENT} from '../paper/Rooms';
|
||||
import lines from '@site/lib/lodging-catchphrases.json';
|
||||
import {Hills, Sky} from './Art';
|
||||
@ -103,7 +104,7 @@ export function Hero() {
|
||||
const {place, narrative} = payload;
|
||||
const phrase = useCatchphrase();
|
||||
const region = [place.addressRegion, place.addressLocality].filter(Boolean).join(' ');
|
||||
const fixed = narrative.tagline ?? narrative.heroSubline;
|
||||
const sub = heroSub(payload);
|
||||
const stay = place.category === PlaceCategory.LODGING ? stayBookingView(payload) : null;
|
||||
const link = bookingLinks(payload)[0];
|
||||
|
||||
@ -111,7 +112,6 @@ export function Hero() {
|
||||
<section className="scene" id="top" data-pg="home" aria-labelledby="hero-heading">
|
||||
<Sky />
|
||||
<div className="hero-copy">
|
||||
{region && <span className="tag">{region}</span>}
|
||||
<h1 id="hero-heading" className="disp">
|
||||
<Pop text={place.name} />
|
||||
</h1>
|
||||
@ -120,7 +120,7 @@ export function Hero() {
|
||||
<Pop text={phrase} />
|
||||
</p>
|
||||
)}
|
||||
{fixed && <p className="sub">{fixed}</p>}
|
||||
{sub && <p className="sub">{sub}</p>}
|
||||
{(stay || link || place.phone) && (
|
||||
<div className="hgo">
|
||||
{stay ? (
|
||||
|
||||
10
solution/site/src/layouts/kit/heroSub.ts
Normal file
10
solution/site/src/layouts/kit/heroSub.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type {SitePayload} from '@o2o/shared';
|
||||
|
||||
export function heroSub(payload: SitePayload): string | undefined {
|
||||
const fixed = payload.narrative.tagline ?? payload.narrative.heroSubline;
|
||||
if (fixed) return fixed;
|
||||
const nearest = [...payload.local.attractions]
|
||||
.filter((spot) => spot.distanceText)
|
||||
.sort((a, b) => (a.distanceMeters ?? Infinity) - (b.distanceMeters ?? Infinity))[0];
|
||||
return nearest ? `${nearest.name}까지 ${nearest.distanceText}` : undefined;
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
import {PlaceCategory} from '@o2o/shared';
|
||||
import {HeroCatchphrase} from '@site/sections/HeroCatchphrase';
|
||||
import {useSite} from '@site/lib/site-context';
|
||||
import {bookingActionLabel, bookingLinks, heroFacts, isSectionEnabled, stayBookingView} from '@site/lib/derive';
|
||||
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';
|
||||
@ -17,10 +18,10 @@ export function Hero() {
|
||||
.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;
|
||||
const chips = heroFacts(payload).map((row) => `${row.label} ${row.value}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -56,16 +57,7 @@ export function Hero() {
|
||||
) : (
|
||||
fixed && <p className="hl in">{fixed}</p>
|
||||
)}
|
||||
{lodging && fixed && <p className="tagline">{fixed}</p>}
|
||||
{chips.length > 0 && (
|
||||
<div className="chips">
|
||||
{chips.map((chip) => (
|
||||
<span key={chip} className="chip">
|
||||
{chip}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{lodging && sub && <p className="tagline">{sub}</p>}
|
||||
<div className="hero-cta">
|
||||
{stay ? (
|
||||
<button type="button" className="btn" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {HeroCatchphrase} from '@site/sections/HeroCatchphrase';
|
||||
import {useSite} from '@site/lib/site-context';
|
||||
import {isSectionEnabled} from '@site/lib/derive';
|
||||
import {heroSub} from '../kit/heroSub';
|
||||
import {Weather} from './Weather';
|
||||
import {Car} from './Car';
|
||||
|
||||
@ -13,7 +14,7 @@ export function Hero() {
|
||||
.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 sub = heroSub(payload);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -41,10 +42,10 @@ export function Hero() {
|
||||
<p className="eyebrow">{place.addressLocality ?? place.addressRegion}</p>
|
||||
)}
|
||||
<h1 className="mj">{place.name}</h1>
|
||||
{fixed && <p className="tagline">{fixed}</p>}
|
||||
<p className="cp">
|
||||
<HeroCatchphrase />
|
||||
</p>
|
||||
{sub && <p className="tagline">{sub}</p>}
|
||||
</div>
|
||||
</section>
|
||||
{isSectionEnabled(payload, 'weather') && (
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import {PlaceCategory} from '@o2o/shared';
|
||||
import {HeroCatchphrase} from '@site/sections/HeroCatchphrase';
|
||||
import {useSite} from '@site/lib/site-context';
|
||||
import {bookingActionLabel, bookingLinks, heroFacts, isSectionEnabled, stayBookingView} from '@site/lib/derive';
|
||||
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';
|
||||
@ -18,13 +19,9 @@ export function Hero() {
|
||||
const fixed = narrative.tagline ?? narrative.heroSubline;
|
||||
const lodging = place.category === PlaceCategory.LODGING;
|
||||
const region = place.addressLocality ?? place.addressRegion;
|
||||
const nearest = [...payload.local.attractions]
|
||||
.filter((spot) => spot.distanceText)
|
||||
.sort((a, b) => (a.distanceMeters ?? Infinity) - (b.distanceMeters ?? Infinity))[0];
|
||||
const sub = fixed ?? (nearest ? `${nearest.name}까지 ${nearest.distanceText}` : undefined);
|
||||
const sub = heroSub(payload);
|
||||
const booking = bookingLinks(payload)[0];
|
||||
const stay = lodging ? stayBookingView(payload) : null;
|
||||
const chips = heroFacts(payload).map((row) => `${row.label} ${row.value}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -61,15 +58,6 @@ export function Hero() {
|
||||
fixed && <p className="hl in">{fixed}</p>
|
||||
)}
|
||||
{lodging && sub && <p className="tagline">{sub}</p>}
|
||||
{chips.length > 0 && (
|
||||
<div className="chips">
|
||||
{chips.map((chip) => (
|
||||
<span key={chip} className="chip">
|
||||
{chip}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="hero-cta">
|
||||
{stay ? (
|
||||
<button type="button" className="btn" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import {PlaceCategory} from '@o2o/shared';
|
||||
import {HeroCatchphrase} from '@site/sections/HeroCatchphrase';
|
||||
import {useSite} from '@site/lib/site-context';
|
||||
import {bookingActionLabel, bookingLinks, heroFacts, isSectionEnabled, stayBookingView} from '@site/lib/derive';
|
||||
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';
|
||||
@ -17,10 +18,10 @@ export function Hero() {
|
||||
.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;
|
||||
const chips = heroFacts(payload).map((row) => `${row.label} ${row.value}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -56,16 +57,7 @@ export function Hero() {
|
||||
) : (
|
||||
fixed && <p className="hl in">{fixed}</p>
|
||||
)}
|
||||
{lodging && fixed && <p className="tagline">{fixed}</p>}
|
||||
{chips.length > 0 && (
|
||||
<div className="chips">
|
||||
{chips.map((chip) => (
|
||||
<span key={chip} className="chip">
|
||||
{chip}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{lodging && sub && <p className="tagline">{sub}</p>}
|
||||
<div className="hero-cta">
|
||||
{stay ? (
|
||||
<button type="button" className="btn" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user