From a0b14dfca13ca4425376fec89025caf5168e0738 Mon Sep 17 00:00:00 2001
From: Mina Choi
Date: Wed, 30 Sep 2026 17:13:24 +0900
Subject: [PATCH] =?UTF-8?q?[fix]=20solution/site,shared:=20=EC=B2=AB=20?=
=?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9D=84=20=EB=AA=A9=EC=97=85=20=EA=B5=AC?=
=?UTF-8?q?=EC=A1=B0=EB=A1=9C=20=E2=80=94=20=EC=BA=90=EC=B9=98=ED=94=84?=
=?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=A6=88=20=ED=95=98=EB=82=98=20=EA=B3=A0?=
=?UTF-8?q?=EC=A0=95,=20=EC=95=84=EB=9E=98=20=EC=A4=84=20=EC=88=9C?=
=?UTF-8?q?=ED=99=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
/s/stay 목업은 캐치프레이즈(tagline) 한 줄이 고정이고 그 아래 줄에서 문구가 돈다
(2026-09-11 "메인 모토는 고정, 그 아래 변동되는 모토"). 템플릿은 큰 줄에 공통 문구 10개를
돌리고 있었고, 앞 커밋의 소문구는 "명소까지 거리" 로 목업과 달랐다.
- sections/HeroCatchphrase.tsx: HeroCatchphrase = tagline 고정(없으면 공통 문구 중 사이트별 하나),
HeroRotator = narrative.catchphrases 를 일반→계절→일반→월→일반→날씨 순서로 6초마다, 줄 높이는 가장 긴 문구
- 9종 Hero: 소문구 자리를 HeroRotator 로. kit/heroSub.ts 삭제
- shared: catchphrases 항목에 season·month·weather
- 테스트: 고정·폴백·비숙박·빈 순환·순서·날씨 분류
tsc·eslint 통과 · vitest 127 passed · 9종 모바일 캡처, 라운드·부티크 6초 간격 순환·높이 고정 확인
Co-Authored-By: Claude Opus 5.5
---
docs/TEMPLATE_DESIGN.md | 5 +-
solution/shared/src/types/site-payload.ts | 13 +-
solution/site/src/layouts/bigtype/Hero.tsx | 6 +-
solution/site/src/layouts/boutique/Hero.tsx | 6 +-
solution/site/src/layouts/cinema/Hero.tsx | 8 +-
solution/site/src/layouts/coral/Hero.tsx | 6 +-
solution/site/src/layouts/graphic/Hero.tsx | 29 +---
solution/site/src/layouts/kit/heroSub.ts | 10 --
solution/site/src/layouts/minimal/Hero.tsx | 6 +-
solution/site/src/layouts/paper/Hero.tsx | 6 +-
solution/site/src/layouts/pine/Hero.tsx | 6 +-
solution/site/src/layouts/round/Hero.tsx | 6 +-
.../site/src/sections/HeroCatchphrase.tsx | 144 ++++++++++++++----
.../site/src/sections/hero-catchphrase.css | 14 +-
.../src/sections/hero-catchphrase.test.tsx | 61 ++++++--
15 files changed, 212 insertions(+), 114 deletions(-)
delete mode 100644 solution/site/src/layouts/kit/heroSub.ts
diff --git a/docs/TEMPLATE_DESIGN.md b/docs/TEMPLATE_DESIGN.md
index 6125d3d..37fa678 100644
--- a/docs/TEMPLATE_DESIGN.md
+++ b/docs/TEMPLATE_DESIGN.md
@@ -85,7 +85,10 @@
## 6. 값 보여주기
-- **첫 화면은 캐치프레이즈 → 그 아래 소문구.** 소문구는 사장님 문구, 없으면 가장 가까운 명소까지 거리(`kit/heroSub.ts`)
+- **첫 화면은 캐치프레이즈 한 줄(고정) → 그 아래 순환 문구.** `/s/stay` 목업과 같다(`sections/HeroCatchphrase.tsx`)
+ - 캐치프레이즈 = `narrative.tagline`. 없으면 공통 문구 10개 중 사이트마다 정해진 하나
+ - 순환 = `narrative.catchphrases`, 일반 → 계절 → 일반 → 월 → 일반 → 날씨 순서, 6초. 없으면 줄을 두지 않는다
+ - 두 값은 COPY 잡이 소개문과 함께 만든다(fact `tagline` · `catchphrases`, `services/copy_steps.py`)
- 첫 화면에 태그·칩(‘주차 가능’, 지역 말풍선)을 두지 않는다
- 부티크 첫 화면은 모바일도 문(아치) 모양이다. 전체화면 사진으로 되돌리지 않는다
diff --git a/solution/shared/src/types/site-payload.ts b/solution/shared/src/types/site-payload.ts
index eebf9c8..144df9d 100644
--- a/solution/shared/src/types/site-payload.ts
+++ b/solution/shared/src/types/site-payload.ts
@@ -322,10 +322,19 @@ export interface RouteEntry {
}
export interface Narrative {
- /** 히어로 둘째 줄에서 순환할 문구. */
+ /** 히어로 둘째 줄에서 순환할 문구. 첫째 줄(캐치프레이즈)은 `tagline` 하나로 고정이다. */
catchphrases?: {
version: 1;
- items: {text: string; kind?: 'general' | 'season' | 'month' | 'weather'}[];
+ items: {
+ text: string;
+ kind?: 'general' | 'season' | 'month' | 'weather';
+ /** kind=season 일 때 봄·여름·가을·겨울. */
+ season?: string;
+ /** kind=month 일 때 1~12. */
+ month?: number;
+ /** kind=weather 일 때 맑음·흐림·비·눈. */
+ weather?: string;
+ }[];
};
heroHeadline?: string;
diff --git a/solution/site/src/layouts/bigtype/Hero.tsx b/solution/site/src/layouts/bigtype/Hero.tsx
index 2fb7df6..45b64b1 100644
--- a/solution/site/src/layouts/bigtype/Hero.tsx
+++ b/solution/site/src/layouts/bigtype/Hero.tsx
@@ -1,8 +1,7 @@
import {PlaceCategory} from '@o2o/shared';
-import {HeroCatchphrase} from '@site/sections/HeroCatchphrase';
+import {HeroCatchphrase, HeroRotator} 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';
@@ -13,7 +12,6 @@ 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];
@@ -31,7 +29,7 @@ export function Hero() {
오늘의 문장
{(lodging || fixed) && {lodging ? : fixed}
}
- {lodging && sub && {sub}
}
+
{stay ? (