merge: feat/templates-all-industries(업종 확장 본체) 을 main 에 병합

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Mina Choi 2026-09-30 14:58:29 +09:00
commit e5153eb66c
20 changed files with 116 additions and 62 deletions

View File

@ -502,7 +502,15 @@
"simple",
"magazine",
"retro",
"paper"
"paper",
"round",
"cinema",
"bigtype",
"boutique",
"graphic",
"coral",
"minimal",
"pine"
],
"sections": [
{
@ -629,7 +637,15 @@
"simple",
"magazine",
"retro",
"paper"
"paper",
"round",
"cinema",
"bigtype",
"boutique",
"graphic",
"coral",
"minimal",
"pine"
],
"sections": [
{
@ -754,7 +770,15 @@
"defaultTemplate": "simple",
"templates": [
"simple",
"magazine"
"magazine",
"round",
"cinema",
"bigtype",
"boutique",
"graphic",
"coral",
"minimal",
"pine"
],
"sections": [
{

View File

@ -30,7 +30,7 @@ type PageId = 'home' | 'area' | 'stay' | 'story';
const PAGE_HEAD: Record<Exclude<PageId, 'home'>, {title: string; note: string}> = {
area: {title: '지역', note: '대문에서 걸어 닿는 자리와, 이 도시가 남긴 이야기입니다.'},
stay: {title: '이용안내', note: '객실 상세와 요금, 예약하는 방법입니다.'},
stay: {title: '이용안내', note: '상세 안내와 요금, 예약하는 방법입니다.'},
story: {title: '이야기', note: '사장님이 남기는 글과 다녀가신 분들의 후기, 그리고 엽서 한 장입니다.'},
};

View File

@ -1,9 +1,10 @@
import {useState} from 'react';
import {factText, sanitizeUnits} from '@o2o/shared';
import {factText, sanitizeUnits, PlaceCategory} from '@o2o/shared';
import {useSite} from '@site/lib/site-context';
import {sectionName, unitSpec, unitViews, type UnitView} from '@site/lib/derive';
import {Car} from '../paper/Car';
import {BOOK_EVENT} from '../paper/Rooms';
import {unitWord} from '../kit/unitWord';
import {Sheet} from '../paper/Sheet';
import {Idx} from './SectionHead';
@ -32,6 +33,7 @@ function specRows(unit: UnitView, facts: Parameters<typeof factText>[0]) {
export function Rooms() {
const payload = useSite();
const lodging = payload.place.category === PlaceCategory.LODGING;
const units = unitViews(payload);
const spec = unitSpec(payload);
const raw = new Map(sanitizeUnits(payload.units).map((unit) => [unit.unitId, unit]));
@ -78,7 +80,7 @@ export function Rooms() {
const [main, sub] = splitName(unit.name);
return (
<section key={unit.unitId} id={`unit-${unit.slug}`}>
<Idx title={main} aside={`Room ${letter(i)}${sub ? ` · ${sub}` : ''}`} />
<Idx title={main} aside={`${unitWord(payload.place.category)} ${letter(i)}${sub ? ` · ${sub}` : ''}`} />
{unit.images.length > 0 && (
<Car
label={`${main} 사진`}
@ -104,6 +106,7 @@ export function Rooms() {
<span>{main} 자세히</span>
<span>+</span>
</button>
{lodging && (
<button
type="button"
className="line line--ink"
@ -112,6 +115,7 @@ export function Rooms() {
<span>{main} 예약 요청</span>
<span>→</span>
</button>
)}
</section>
);
})}

View File

@ -30,7 +30,7 @@ const PAGE_HEAD: Record<Exclude<PageId, 'home'>, {en: string; title: string; not
stay: {
en: 'Stay',
title: '이용안내 · 예약',
note: '객실 상세와 요금, 예약하는 방법입니다.',
note: '상세 안내와 요금, 예약하는 방법입니다.',
},
story: {
en: 'Story',
@ -131,6 +131,7 @@ function useTabs() {
export function Frame({children}: {children: ReactNode}) {
const payload = useSite();
const {place, site} = payload;
const lodging = place.category === PlaceCategory.LODGING;
const tabs = useTabs();
const page = usePage();
const solid = useSolid(page);
@ -245,7 +246,7 @@ export function Frame({children}: {children: ReactNode}) {
onClick={() => setMenuOpen(false)}
>
<span className="b-menu-no">{pad2(i + 1)}</span>
<span className="b-menu-en">{PAGE_EN[tab.page]}</span>
<span className="b-menu-en">{tab.page === 'stay' && !lodging ? 'Guide' : PAGE_EN[tab.page]}</span>
<span className="b-menu-ko">{tab.label}</span>
</a>
))}
@ -254,7 +255,7 @@ export function Frame({children}: {children: ReactNode}) {
{(Object.keys(PAGE_HEAD) as Exclude<PageId, 'home'>[]).map((id) => (
<div key={id} data-pg={id}>
<section className="b-phead">
<p className="b-phead-en">{PAGE_HEAD[id].en}</p>
<p className="b-phead-en">{id === 'stay' && !lodging ? 'Guide' : PAGE_HEAD[id].en}</p>
<p className="b-phead-ko">
{id === 'area' ? (place.addressLocality?.replace(/(시|군)$/, '') ?? PAGE_HEAD.area.title) : PAGE_HEAD[id].title}
</p>

View File

@ -31,7 +31,7 @@ export function Blog({full}: {full?: boolean}) {
return (
<section className="b-sec" id={full ? 'blog' : undefined} aria-labelledby={full ? 'blog-heading' : undefined}>
<Sh en="Journal" id={full ? 'blog-heading' : undefined} lead="사장님이 남기는 숙소 이야기입니다.">
<Sh en="Journal" id={full ? 'blog-heading' : undefined} lead="사장님이 직접 남기는 소식입니다.">
미니 블로그
</Sh>
<div className="b-wrap">

View File

@ -1,9 +1,10 @@
import {useState} from 'react';
import {factText, sanitizeUnits} from '@o2o/shared';
import {factText, sanitizeUnits, PlaceCategory} from '@o2o/shared';
import {useSite} from '@site/lib/site-context';
import {sectionName, unitSpec, unitViews, type UnitView} from '@site/lib/derive';
import {Car} from '../paper/Car';
import {BOOK_EVENT} from '../paper/Rooms';
import {unitWord} from '../kit/unitWord';
import {Sheet} from '../paper/Sheet';
function splitName(name: string): [string, string | undefined] {
@ -31,6 +32,7 @@ const book = () => window.dispatchEvent(new Event(BOOK_EVENT));
export function Rooms() {
const payload = useSite();
const lodging = payload.place.category === PlaceCategory.LODGING;
const units = unitViews(payload);
const spec = unitSpec(payload);
const raw = new Map(sanitizeUnits(payload.units).map((unit) => [unit.unitId, unit]));
@ -43,7 +45,7 @@ export function Rooms() {
<div data-pg="home">
<section id="units" aria-labelledby="units-heading">
<div className="wrap">
<p className="label">ROOMS</p>
<p className="label">{unitWord(payload.place.category).toUpperCase()}</p>
<h2 id="units-heading">{sectionName(payload, spec.path, `${spec.label} 안내`)}</h2>
</div>
<div className="wrap">
@ -77,7 +79,7 @@ export function Rooms() {
return (
<section key={unit.unitId} className="unit" id={`unit-${unit.slug}`}>
<div className="wrap">
<p className="label">ROOM {String(i + 1).padStart(2, '0')}</p>
<p className="label">{unitWord(payload.place.category).toUpperCase()} {String(i + 1).padStart(2, '0')}</p>
<h2>
{main} {sub && <span className="cnt">{sub}</span>}
</h2>
@ -109,9 +111,11 @@ export function Rooms() {
<button type="button" className="btn ghost" onClick={() => setOpen(unit)}>
{main} 자세히
</button>
{lodging && (
<button type="button" className="btn" onClick={book}>
{main} 예약 요청
</button>
)}
</div>
</div>
</section>

View File

@ -36,7 +36,7 @@ const PAGE_HEAD: Record<Exclude<PageId, 'home'>, {label: string; title: string;
stay: {
label: 'STAY',
title: '이용안내 · 예약',
note: '객실 상세와 요금, 예약하는 방법입니다.',
note: '상세 안내와 요금, 예약하는 방법입니다.',
},
story: {
label: 'STORY',

View File

@ -30,7 +30,7 @@ function Blog({full}: {full?: boolean}) {
<section className="wrap" id={full ? 'blog' : undefined}>
<p className="label">JOURNAL</p>
<h2>미니 블로그</h2>
<p className="lead">사장님이 남기는 숙소 이야기입니다.</p>
<p className="lead">사장님이 직접 남기는 소식입니다.</p>
<div className="posts" data-blog={full ? 'full' : 'home'}>
{shown.map((item) => (
<Post key={item.postId} publishedAt={item.publishedAt} body={item.body} />

View File

@ -1,9 +1,10 @@
import {useState} from 'react';
import {factText, sanitizeUnits} from '@o2o/shared';
import {factText, sanitizeUnits, PlaceCategory} from '@o2o/shared';
import {useSite} from '@site/lib/site-context';
import {sectionName, unitSpec, unitViews, type UnitView} from '@site/lib/derive';
import {Car} from '../paper/Car';
import {BOOK_EVENT} from '../paper/Rooms';
import {unitWord} from '../kit/unitWord';
import {Sheet} from '../paper/Sheet';
function splitName(name: string): [string, string | undefined] {
@ -32,6 +33,7 @@ const book = () => window.dispatchEvent(new Event(BOOK_EVENT));
export function Rooms() {
const payload = useSite();
const lodging = payload.place.category === PlaceCategory.LODGING;
const units = unitViews(payload);
const spec = unitSpec(payload);
const raw = new Map(sanitizeUnits(payload.units).map((unit) => [unit.unitId, unit]));
@ -45,7 +47,7 @@ export function Rooms() {
<div data-pg="home">
<section id="units" aria-labelledby="units-heading">
<div className="wrap">
<p className="label">ROOMS</p>
<p className="label">{unitWord(payload.place.category).toUpperCase()}</p>
<h2 id="units-heading">{sectionName(payload, spec.path, `${spec.label} 안내`)}</h2>
<div className="rgrid">
{units.slice(0, HOME_UNITS).map((unit) => (
@ -69,7 +71,7 @@ export function Rooms() {
return (
<section key={unit.unitId} className="unit" id={`unit-${unit.slug}`}>
<div className="wrap">
<p className="label">ROOM {String(i + 1).padStart(2, '0')}</p>
<p className="label">{unitWord(payload.place.category).toUpperCase()} {String(i + 1).padStart(2, '0')}</p>
<h2>
{main} {sub && <span className="cnt">{sub}</span>}
</h2>
@ -101,9 +103,11 @@ export function Rooms() {
<button type="button" className="btn ghost" onClick={() => setOpen(unit)}>
{main} 자세히
</button>
{lodging && (
<button type="button" className="btn" onClick={book}>
{main} 예약 요청
</button>
)}
</div>
</div>
</section>

View File

@ -36,7 +36,7 @@ const PAGE_HEAD: Record<Exclude<PageId, 'home'>, {kicker: string; title: string;
stay: {
kicker: 'Reservation',
title: '이용안내 · 예약',
note: '객실 상세와 요금, 예약하는 방법입니다.',
note: '상세 안내와 요금, 예약하는 방법입니다.',
},
story: {
kicker: 'Story',

View File

@ -1,9 +1,10 @@
import {useState} from 'react';
import {factText, sanitizeUnits} from '@o2o/shared';
import {factText, sanitizeUnits, PlaceCategory} from '@o2o/shared';
import {useSite} from '@site/lib/site-context';
import {sectionName, unitSpec, unitViews, type UnitView} from '@site/lib/derive';
import {Car} from '../paper/Car';
import {BOOK_EVENT} from '../paper/Rooms';
import {unitWord} from '../kit/unitWord';
import {Sheet} from '../paper/Sheet';
function splitName(name: string): [string, string | undefined] {
@ -30,6 +31,7 @@ const HOME_MAX = 4;
export function Rooms() {
const payload = useSite();
const lodging = payload.place.category === PlaceCategory.LODGING;
const units = unitViews(payload);
const spec = unitSpec(payload);
const raw = new Map(sanitizeUnits(payload.units).map((unit) => [unit.unitId, unit]));
@ -42,7 +44,7 @@ export function Rooms() {
<>
<div data-pg="home">
<section className="sec" id="units" aria-labelledby="units-heading">
<p className="kicker">Room View</p>
<p className="kicker">{lodging ? 'Room View' : unitWord(payload.place.category)}</p>
<h2 id="units-heading">{sectionName(payload, spec.path, `${spec.label} 안내`)}</h2>
<div className="rooms">
{units.slice(0, HOME_MAX).map((unit, i) => (
@ -74,7 +76,7 @@ export function Rooms() {
return (
<section key={unit.unitId} className="sec" id={`unit-${unit.slug}`}>
<p className="kicker">
Room {String(i + 1).padStart(2, '0')}
{unitWord(payload.place.category)} {String(i + 1).padStart(2, '0')}
</p>
<h2>
{main} {sub && <span className="sub">{sub}</span>}
@ -105,9 +107,11 @@ export function Rooms() {
<button type="button" className="btn ghost" onClick={() => setOpen(unit)}>
자세히
</button>
{lodging && (
<button type="button" className="btn" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
예약 요청
</button>
)}
</div>
</div>
</div>

View File

@ -29,7 +29,7 @@ type PageId = 'home' | 'area' | 'stay' | 'story';
const PAGE_HEAD: Record<Exclude<PageId, 'home'>, {title: string; note: string}> = {
area: {title: '지역 소개', note: '대문에서 걸어 닿는 자리와, 이 도시가 남긴 이야기입니다.'},
stay: {title: '이용안내', note: '객실 상세와 요금, 예약하는 방법입니다.'},
stay: {title: '이용안내', note: '상세 안내와 요금, 예약하는 방법입니다.'},
story: {title: '이야기', note: '사장님이 남기는 글과 다녀가신 분들의 후기, 그리고 엽서 한 장입니다.'},
};

View File

@ -1,5 +1,5 @@
import {useState} from 'react';
import {factText, sanitizeUnits} from '@o2o/shared';
import {factText, sanitizeUnits, PlaceCategory} from '@o2o/shared';
import {useSite} from '@site/lib/site-context';
import {sectionName, unitSpec, unitViews, type UnitView} from '@site/lib/derive';
import {Car} from '../paper/Car';
@ -60,6 +60,7 @@ function UnitSheet({unit, onClose}: {unit: UnitView | null; onClose: () => void}
export function Rooms() {
const payload = useSite();
const lodging = payload.place.category === PlaceCategory.LODGING;
const units = unitViews(payload);
const spec = unitSpec(payload);
const raw = new Map(sanitizeUnits(payload.units).map((unit) => [unit.unitId, unit]));
@ -136,9 +137,11 @@ export function Rooms() {
<button type="button" className="more" onClick={() => setOpen(unit)}>
{main} 자세히
</button>
{lodging && (
<button type="button" className="cta" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
{main} 예약 요청
</button>
)}
</div>
</div>
</section>

View File

@ -36,7 +36,7 @@ const PAGE_HEAD: Record<Exclude<PageId, 'home'>, {kicker: string; title: string;
stay: {
kicker: 'Reservation',
title: '이용안내 · 예약',
note: '객실 상세와 요금, 예약하는 방법입니다.',
note: '상세 안내와 요금, 예약하는 방법입니다.',
},
story: {
kicker: 'Story',

View File

@ -1,9 +1,10 @@
import {useState} from 'react';
import {factText, sanitizeUnits} from '@o2o/shared';
import {factText, sanitizeUnits, PlaceCategory} from '@o2o/shared';
import {useSite} from '@site/lib/site-context';
import {sectionName, unitSpec, unitViews, type UnitView} from '@site/lib/derive';
import {Car} from '../paper/Car';
import {BOOK_EVENT} from '../paper/Rooms';
import {unitWord} from '../kit/unitWord';
import {Sheet} from '../paper/Sheet';
function splitName(name: string): [string, string | undefined] {
@ -30,6 +31,7 @@ const HOME_MAX = 4;
export function Rooms() {
const payload = useSite();
const lodging = payload.place.category === PlaceCategory.LODGING;
const units = unitViews(payload);
const spec = unitSpec(payload);
const raw = new Map(sanitizeUnits(payload.units).map((unit) => [unit.unitId, unit]));
@ -42,7 +44,7 @@ export function Rooms() {
<>
<div data-pg="home">
<section className="sec" id="units" aria-labelledby="units-heading">
<p className="kicker">Stay</p>
<p className="kicker">{lodging ? 'Stay' : unitWord(payload.place.category)}</p>
<h2 id="units-heading">{sectionName(payload, spec.path, `${spec.label} 안내`)}</h2>
<div className="rooms">
{units.slice(0, HOME_MAX).map((unit) => (
@ -71,7 +73,7 @@ export function Rooms() {
return (
<section key={unit.unitId} className="sec" id={`unit-${unit.slug}`}>
<p className="kicker">
Room {String(i + 1).padStart(2, '0')}
{unitWord(payload.place.category)} {String(i + 1).padStart(2, '0')}
</p>
<h2>
{main} {sub && <span className="sub">{sub}</span>}
@ -102,9 +104,11 @@ export function Rooms() {
<button type="button" className="btn ghost" onClick={() => setOpen(unit)}>
자세히
</button>
{lodging && (
<button type="button" className="btn" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
예약 요청
</button>
)}
</div>
</div>
</div>

View File

@ -22,7 +22,7 @@ export function Blog({full}: {full?: boolean}) {
<p className="eyebrow">BLOG</p>
<h2>미니 블로그</h2>
<p className="note" style={{margin: '-8px 0 14px'}}>
사장님이 남기는 숙소 이야기입니다.
사장님이 직접 남기는 소식입니다.
</p>
<div data-blog={full ? 'full' : 'home'}>
{shown.map((item) => (

View File

@ -36,7 +36,7 @@ const PAGE_HEAD: Record<Exclude<PageId, 'home'>, {kicker: string; title: string;
stay: {
kicker: '이용안내',
title: '이용안내 · 예약',
note: '객실 상세와 요금, 예약하는 방법입니다.',
note: '상세 안내와 요금, 예약하는 방법입니다.',
},
story: {
kicker: '이야기',

View File

@ -1,5 +1,5 @@
import {useState} from 'react';
import {factText, sanitizeUnits} from '@o2o/shared';
import {factText, sanitizeUnits, PlaceCategory} from '@o2o/shared';
import {useSite} from '@site/lib/site-context';
import {sectionName, unitSpec, unitViews, type UnitView} from '@site/lib/derive';
import {Car} from '../paper/Car';
@ -30,6 +30,7 @@ const HOME_MAX = 4;
export function Rooms() {
const payload = useSite();
const lodging = payload.place.category === PlaceCategory.LODGING;
const units = unitViews(payload);
const spec = unitSpec(payload);
const raw = new Map(sanitizeUnits(payload.units).map((unit) => [unit.unitId, unit]));
@ -102,9 +103,11 @@ export function Rooms() {
<button type="button" className="btn ghost" onClick={() => setOpen(unit)}>
자세히
</button>
{lodging && (
<button type="button" className="btn" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
예약 요청
</button>
)}
</div>
</div>
</div>

View File

@ -36,7 +36,7 @@ const PAGE_HEAD: Record<Exclude<PageId, 'home'>, {kicker: string; title: string;
stay: {
kicker: '이용안내',
title: '이용안내 · 예약',
note: '객실 상세와 요금, 예약하는 방법입니다.',
note: '상세 안내와 요금, 예약하는 방법입니다.',
},
story: {
kicker: '이야기',

View File

@ -1,5 +1,5 @@
import {useState} from 'react';
import {factText, sanitizeUnits} from '@o2o/shared';
import {factText, sanitizeUnits, PlaceCategory} from '@o2o/shared';
import {useSite} from '@site/lib/site-context';
import {sectionName, unitSpec, unitViews, type UnitView} from '@site/lib/derive';
import {Car} from '../paper/Car';
@ -30,6 +30,7 @@ const HOME_MAX = 4;
export function Rooms() {
const payload = useSite();
const lodging = payload.place.category === PlaceCategory.LODGING;
const units = unitViews(payload);
const spec = unitSpec(payload);
const raw = new Map(sanitizeUnits(payload.units).map((unit) => [unit.unitId, unit]));
@ -102,9 +103,11 @@ export function Rooms() {
<button type="button" className="btn ghost" onClick={() => setOpen(unit)}>
자세히
</button>
{lodging && (
<button type="button" className="btn" onClick={() => window.dispatchEvent(new Event(BOOK_EVENT))}>
예약 요청
</button>
)}
</div>
</div>
</div>