수집한 객실·이용 정보가 발행 화면에 연결되지 않던 경로를 보완하고, 숙소 소개와 지역 맛집 표시를 개선한다. - NOL 브라우저 수집 어댑터와 수집·반영 스크립트 추가 - 크롤링 fact 즉시 노출 및 직접 입력·정정값 보호 - 이용안내 항목별 구조화와 기존 표 연결, 원문 UI 비표시 - 군산 한일옥 고정 등록과 지역 맛집 탐색·보강 경로 추가 - 숙소 소개 요약, 히어로 문구, 지역 콘텐츠·목업 표시 개선 검증: 작업 트리 기준 site 타입·린트·빌드 및 안내 렌더링 테스트 통과, PC·모바일 화면 확인. 스테이징 diff 공백 검사 통과. 사용자 요청에 따라 현재 스테이징된 55개 파일만 포함하며 미스테이징 문서·테스트 등은 제외.
401 lines
20 KiB
TypeScript
401 lines
20 KiB
TypeScript
import {HeroCatchphrase} from '@site/sections/HeroCatchphrase';
|
|
import type {CSSProperties, ReactNode} from 'react';
|
|
import {isoDate} from '@site/lib/format';
|
|
import {useSite} from '@site/lib/site-context';
|
|
import {
|
|
bookingActionLabel,
|
|
bookingLabel,
|
|
bookingLinks,
|
|
contactLinks,
|
|
heroFacts,
|
|
isSectionEnabled,
|
|
unitSpec,
|
|
} from '@site/lib/derive';
|
|
// ★ 배럴(`@/sections`)을 거치면 껍데기 하나가 사이트의 모든 섹션을 끌고 들어온다.
|
|
// 껍데기는 하단 고정 바 하나만 필요하다 — 파일을 직접 가리킨다.
|
|
import {MobileTabBar} from '@site/sections/MobileTabBar';
|
|
|
|
/**
|
|
* 본문 섹션의 색 띠를 걷어낸다 (2026-09-04, 사장님 지적).
|
|
*
|
|
* ★ 실측: 히어로는 투명(바탕 #e5ddd2)인데 소개가 rgb(219,211,200), 예약 전 확인이
|
|
* rgb(222,213,200), 객실이 다시 투명, 요금·이용정보가 rgb(222,213,200) 이었다.
|
|
* 밝기가 오르락내리락하는 띠가 여덟 번 반복된다. 이 안에는 **면이 없다** —
|
|
* Hero 주석의 "버튼 상자를 만들지 않는다"와 같은 규칙이 섹션에도 걸려야 한다.
|
|
* ★ !important 인 이유: 공용 Section 이 배경색과 세로 여백을 **인라인 style** 로 박는다
|
|
* (lib/ui/Section.tsx:82). 인라인은 선택자 특정도로 못 이기므로 이 자리에서만 강제한다.
|
|
* 제 폴더가 직접 그린 판(Hero·Rooms)은 `data-oasi` 로 표시해 건너뛴다 — 이미 투명이고
|
|
* 위 여백을 스스로 정한다.
|
|
* ★ 글자색도 같이 강제한다. tone='dark' 섹션이 인라인으로 밝은 글자를 넣는데,
|
|
* 면이 사라지면 그 글자가 갱지 위에 하얗게 떠서 안 읽힌다.
|
|
* ★ 아래 실선(border-b)도 지운다. 색 띠를 지우고 선만 남기면 이번엔 줄만 여덟 개 그어진다.
|
|
* ★ `.shell` 의 좌우 여백을 0 으로 돌린다. 껍데기가 이미 본문 폭을 잡아 뒀는데
|
|
* 섹션 안에서 한 번 더 들어가서, 실측 1440px 에서 공용 섹션 제목만 40px 안쪽에
|
|
* 있었다(히어로·객실 569px ↔ 소개·요금 609px).
|
|
*
|
|
* ★ 뒤의 두 규칙은 **본문 칸이 716px 로 고정**이라 생기는 어긋남을 되돌린다.
|
|
* 공용 섹션은 1200px 지면을 전제로 `lg:`·`xl:` 에서 칸을 나누는데, 그 중단점은
|
|
* 화면 폭이지 이 칸의 폭이 아니다. 이 껍데기는 화면이 아무리 넓어도 칸이 안 넓어진다.
|
|
* - #about: 사진 7 · 글 5 로 갈리면 글 칸이 275px 다 — 한 줄에 열네 자.
|
|
* 게다가 세로 가운데 정렬이라 짧은 사진이 허공에 뜨고 위아래로 200px 씩 빈다.
|
|
* → 위아래로 쌓아 사진도 글도 칸을 다 쓰게 한다(글은 .measure 가 줄 길이를 잡는다).
|
|
* - #summary: 값 표가 xl 에서 두 칸으로 갈려 한 칸이 179px 이 됐다.
|
|
* 환불 규정 한 문장이 여덟 줄로 감긴다 → 한 칸으로 되돌린다.
|
|
*
|
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
* 남아 있던 마지막 면 — 공용 `.panel` (2026-09-04, 사장님 "다 고쳐")
|
|
*
|
|
* ★ 섹션 띠를 지우고 나니 `.panel`(index.css:197, 글자색 5% 틴트 + 실선)만 남아
|
|
* 이 안에서 유일하게 떠 있는 상자가 됐다. 실측 64개 — FAQ 32 · 주변 24 · 축제 4 ·
|
|
* 이용정보 2 · 오시는 길 1 · 날씨 1.
|
|
* ★ 그냥 지우면 FAQ 서른둘이 구분 없는 글 덩어리가 된다. 그래서 **면을 걷고 구분을
|
|
* 다시 세운다** — 인쇄물이 상자 없이 목록을 가르는 두 가지, 가는 괘선과 들여쓰기다.
|
|
* ★ !important 를 쓰지 않는다. 이 <style> 은 캐스케이드 레이어 **밖**이라
|
|
* `@layer components` 의 `.panel` 도 `@layer utilities` 의 `px-5` 도 그냥 이긴다.
|
|
* 위쪽 규칙들이 !important 인 건 인라인 style 을 이겨야 해서지 특정도 때문이 아니다.
|
|
* ★ `.oasi-page` 밖으로 새면 안 된다 — `index.css` 를 고치면 1~6안이 전부 바뀐다.
|
|
*
|
|
* - #faq: 항목마다 윗줄 괘선 하나. 항목 사이 여백(gap-3)은 0 으로 — 괘선이 경계를
|
|
* 맡는데 여백까지 두면 줄 뜬 상자로 다시 읽힌다. 대신 두 단 사이는 16px → 40px:
|
|
* 실측 본문 칸이 716px 뿐이라 16px 이면 두 단의 활자가 한 덩어리로 붙는다.
|
|
* 답변은 질문 첫 글자 자리(1.875rem = 'Q.' 칸 1.125rem + gap-3 0.75rem)에 매달아
|
|
* 들여쓰고, 질문과 답변 사이의 실선은 뺀다 — 괘선이 두 종류면 어느 게 항목 경계인지
|
|
* 눈이 못 고른다. 'Q.' 칸에 폭을 박는 건 들여쓰기 눈금을 맞추기 위해서다(실측 17.4px).
|
|
* - #info: `divide-y` 가 이미 줄 사이를 긋고 있었다. 상자만 걷으면 그게 곧 표다.
|
|
* 좌우 여백(px-5)을 0 으로 돌려 항목 이름이 섹션 제목과 같은 왼쪽 선에 서게 한다.
|
|
* 목록 맨 위에 줄을 **덧긋지 않는다** — 묶음 제목('예약 전 확인')이 이미 밑줄을
|
|
* 달고 있어서, 실측 16px 간격으로 같은 굵기의 줄이 두 개 그어졌다.
|
|
* 값이 전부 짧을 때만 뜨는 격자 판(`dl > div.panel`, 이 payload 에는 안 나온다)은
|
|
* 칸이 따로 떨어져 있어 줄 사이를 잇는 실선이 없다 — 칸마다 윗줄을 하나씩 준다.
|
|
* - #location: 주소 칸도 같은 규칙. 지도 액자(1px 실선)는 **그대로 둔다** —
|
|
* 사진·지도를 실선으로 두르는 건 편집 문법이지 여기서 걷어내는 '틴트 면'이 아니다.
|
|
* 거점 표는 이 payload 에 없지만(routes 0건) 같은 안을 쓰는 다른 사이트에서는 뜬다.
|
|
* - #guide·#festival: 사진이 이미 덩어리를 가른다. 괘선을 더 긋지 않고 캡션의 좌우
|
|
* 여백만 0 으로 — 글이 사진 왼쪽 변에 붙는 인쇄물의 캡션 규칙이다. 축제 카드는
|
|
* sm 아래에서 썸네일이 왼쪽에 서는 가로 배치라 그때는 왼쪽 여백을 남긴다.
|
|
* - #weather: 값이 한 줄이라 가를 것이 없다. 여백만 0 으로.
|
|
*
|
|
* ★ 거점 표의 칸을 `thead`/`tbody` 로 짚은 이유 — **이 문자열도 소스 파일이다.**
|
|
* Tailwind 스캐너는 이 파일의 글자를 전부 훑어 유틸리티 후보를 뽑는다. 선택자에
|
|
* 유틸리티와 이름이 같은 태그 하나를 적었더니 공용 CSS 에 쓰지도 않는 규칙이
|
|
* 하나 구워졌다(실측: 번들 해시가 index-Dm9iRWS8 → index-Ca3hpIbE 로 바뀐다).
|
|
* 해시가 바뀌면 3안 하나 고치자고 **여섯 사이트를 다 다시 구워야** 한다.
|
|
*/
|
|
const PLATE_CSS = `
|
|
.oasi-page main > section:not([data-oasi]) {
|
|
background-color: transparent !important;
|
|
background-image: none !important;
|
|
border-color: transparent !important;
|
|
color: var(--tpl-text) !important;
|
|
padding-block: var(--oasi-gap) 0 !important;
|
|
}
|
|
.oasi-page main > section:not([data-oasi]) .shell {
|
|
max-width: none;
|
|
padding-inline: 0;
|
|
}
|
|
.oasi-page main > section#about > .shell > .grid {
|
|
display: block;
|
|
}
|
|
.oasi-page main > section#about > .shell > .grid > * + * {
|
|
margin-top: 2.5rem;
|
|
}
|
|
.oasi-page main > section#summary dl {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
|
|
/*
|
|
* 붙여넣기 아이템 카드 — 소식 · 영상 · 추천 일정 (2026-09-04, 사장님: "1,2,3안 다 맞추세요")
|
|
*
|
|
* ★ 이 카드들은 .panel 이 아니라 제 인라인 style 로 면을 그린다(items/common.tsx 의
|
|
* ITEM_CARD·ITEM_BORDER). 그래서 위 .panel 규칙이 안 닿았고, 섹션 띠를 다 지운 3안에서
|
|
* **소식 카드만 갱지 결에 2px 테두리를 두른 상자로 혼자 서 있었다.**
|
|
* ★ w4-paper 의 결은 1안(레트로 갱지)의 것이다. 여기서는 결을 지우고 테두리를 실선 한 겹으로
|
|
* 낮춘다. 상자를 아예 없애지는 않는다 — 전단 여섯 장이 경계 없이 붙으면 어디까지가 한 장인지
|
|
* 안 보인다. 면은 빼고 테두리만 남기는 게 이 안의 타협점이다.
|
|
* ★ 배경은 transparent 로 두지 않는다. 사진 없는 공지 카드가 종이에 바로 얹히면
|
|
* 본문과 구분이 사라진다 — 아주 옅은 틴트 한 겹만 남긴다.
|
|
* ★ 영상은 뺐다 (2026-09-04, 사장님: "영상 border 안 예쁘다")
|
|
* 영상 카드는 상자가 아니라 **표지 사진 한 장**이고, 쇼츠라 9:16 세로다. li 에 틴트를 깔면
|
|
* 영상보다 넓은 회색 판이 뒤에 깔려 액자처럼 보인다. 사진은 제 테두리가 곧 경계다.
|
|
*/
|
|
.oasi-page section#event li,
|
|
.oasi-page section#itinerary article {
|
|
background-image: none !important;
|
|
background-color: color-mix(in oklab, currentcolor 3%, transparent) !important;
|
|
border-width: 1px !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
.oasi-page .panel,
|
|
.oasi-page .panel-sunken {
|
|
background-color: transparent;
|
|
border-width: 0;
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.oasi-page main > section#faq > .shell > div:last-of-type {
|
|
row-gap: 0;
|
|
column-gap: 2.5rem;
|
|
}
|
|
.oasi-page main > section#faq details {
|
|
margin-block: 0;
|
|
border-top: 1px solid var(--site-line);
|
|
}
|
|
.oasi-page main > section#faq summary {
|
|
padding-inline: 0;
|
|
}
|
|
.oasi-page main > section#faq summary > span:first-child {
|
|
width: 1.125rem;
|
|
}
|
|
.oasi-page main > section#faq summary + div {
|
|
border-top-width: 0;
|
|
padding-inline: 1.875rem 0;
|
|
padding-top: 0.5rem;
|
|
}
|
|
|
|
.oasi-page main > section#info dl.panel > div {
|
|
padding-inline: 0;
|
|
}
|
|
.oasi-page main > section#info dl > div.panel {
|
|
border-top: 1px solid var(--site-line);
|
|
padding-inline: 0;
|
|
}
|
|
|
|
.oasi-page main > section#location .panel {
|
|
border-top: 1px solid var(--site-line);
|
|
padding-inline: 0;
|
|
}
|
|
.oasi-page main > section#location :is(thead, tbody) :is(th, td):first-child {
|
|
padding-left: 0;
|
|
}
|
|
.oasi-page main > section#location :is(thead, tbody) :is(th, td):last-child {
|
|
padding-right: 0;
|
|
}
|
|
|
|
.oasi-page main > section#weather .panel {
|
|
padding-inline: 0;
|
|
}
|
|
|
|
.oasi-page main > section#guide .panel > span:last-child {
|
|
padding-inline: 0;
|
|
}
|
|
@media (min-width: 40rem) {
|
|
.oasi-page main > section#festival .panel > span:last-child {
|
|
padding-inline: 0;
|
|
}
|
|
}
|
|
`;
|
|
|
|
/**
|
|
* ★ 섹션 사이 여백은 이제 **유일한 경계**다. 색 띠와 실선이 사라졌으니 여백이 좁으면
|
|
* 섹션이 이어 붙어 보인다. 템플릿이 정한 값(--tpl-section-space, 이 안은 7rem)을
|
|
* 상한으로 쓰되 모바일에서는 3.5rem 까지 줄인다.
|
|
* 공용 --section-space(상한 같음, 하한 2.25rem)를 그대로 쓰지 않는 이유는,
|
|
* 그 값은 '위아래 둘 다' 붙던 값이라 경계 하나로 쓰기에는 좁다.
|
|
*/
|
|
const OASI_VARS = {
|
|
'--oasi-gap': 'clamp(3.5rem, 7vw, var(--tpl-section-space, 4rem))',
|
|
backgroundColor: 'var(--tpl-bg)',
|
|
color: 'var(--tpl-text)',
|
|
} as CSSProperties;
|
|
|
|
/**
|
|
* oasi 껍데기 — **왼쪽 세로 메뉴 + 아주 큰 여백 + 작은 글씨.**
|
|
*
|
|
* ★ 다른 안과 갈리는 지점은 '상단 가로 헤더가 없다' 하나다.
|
|
* 메뉴가 왼쪽에 세로로 서면 본문 폭이 좁아지고, 좁은 폭에 작은 글씨를 넣어야
|
|
* 줄 길이가 맞는다 — 여백·글자 크기가 전부 여기서 따라온다.
|
|
* ★ 카드·그림자를 쓰지 않는다. 이 안에서 면을 가르는 건 가는 실선 하나뿐이다.
|
|
* ★ 왼쪽 칼럼은 `sticky` 다 — 본문이 길어도 메뉴가 따라온다. 좁은 화면에서는
|
|
* 위로 올라가 가로 메뉴가 된다(sticky 를 걸면 모바일에서 화면 절반을 먹는다).
|
|
*/
|
|
export function Shell({children}: {children: ReactNode}) {
|
|
const payload = useSite();
|
|
const {place} = payload;
|
|
const spec = unitSpec(payload);
|
|
const booking = bookingLinks(payload)[0];
|
|
const channels = contactLinks(payload);
|
|
|
|
const menu = [
|
|
{label: '처음', href: '#top', show: true},
|
|
{label: '소개', href: '#about', show: isSectionEnabled(payload, 'intro')},
|
|
{label: spec.label, href: '#units', show: payload.units.length > 0},
|
|
{label: '이용 정보', href: '#info', show: true},
|
|
{label: '주변', href: '#guide', show: isSectionEnabled(payload, 'local')},
|
|
{label: '오시는 길', href: '#location', show: true},
|
|
{label: '자주 묻는 질문', href: '#faq', show: payload.faqs.length > 0},
|
|
].filter((item) => item.show);
|
|
|
|
/*
|
|
* 유틸 띠에 놓는 짧은 문구 셋.
|
|
* ★ 문구를 지어내지 않는다 — 확인된 fact 를 먼저 쓰고(체크인 16:00), 모자라면
|
|
* 사장님이 켜 둔 섹션 이름으로 채운다. 둘 다 없으면 띠를 그리지 않는다.
|
|
*/
|
|
const strip = [
|
|
...heroFacts(payload).map((row) => `${row.label} ${row.value}`),
|
|
...payload.theme.sections
|
|
.filter((section) => section.enabled && section.name?.trim())
|
|
.map((section) => section.name.trim()),
|
|
].slice(0, 3);
|
|
|
|
const slogan = payload.narrative.tagline ?? payload.narrative.heroSubline;
|
|
const address = place.roadAddress ?? place.address;
|
|
|
|
return (
|
|
<div className="oasi-page flex min-h-screen w-full flex-col" style={OASI_VARS}>
|
|
<style dangerouslySetInnerHTML={{__html: PLATE_CSS}} />
|
|
|
|
{/* 아래 여백을 위보다 크게 잡는다 — 섹션이 판 없이 여백으로만 끊기는데,
|
|
마지막 섹션과 꼬리 실선 사이가 섹션 간격보다 좁으면 꼬리가 본문에 붙어 보인다. */}
|
|
<div className="mx-auto flex w-full max-w-[72rem] flex-col gap-10 px-5 pb-16 pt-10 lg:flex-row lg:gap-14 lg:px-10 lg:pb-24 lg:pt-16">
|
|
{/* 왼쪽 칼럼 — 워드마크 → 넉넉한 여백 → 세로 메뉴. 300px 가 원본 폭이다. */}
|
|
<header className="lg:sticky lg:top-12 lg:h-fit lg:w-[18.75rem] lg:shrink-0">
|
|
<a href="#top" className="block">
|
|
<span
|
|
className="serif block leading-none"
|
|
/* 자간·굵기를 인라인으로 덮는다 — 템플릿 기본값은 굵고 자간이 음수다.
|
|
이 안의 워드마크는 '가늘고 넓게' 가 정체성이라 그대로 두면 안이 안 산다. */
|
|
style={{
|
|
fontSize: 'var(--fs-display)',
|
|
fontWeight: 300,
|
|
letterSpacing: '0.14em',
|
|
}}
|
|
>
|
|
{place.name}
|
|
</span>
|
|
{place.englishName && (
|
|
<span className="text-muted mt-3 block text-[length:var(--fs-xs)] tracking-[0.24em]">
|
|
{place.englishName}
|
|
</span>
|
|
)}
|
|
</a>
|
|
|
|
<nav aria-label="주요 메뉴" className="mt-12 lg:mt-24">
|
|
{/* 좁은 화면에서는 두 줄로 접힌다 — gap-y 가 없으면 두 줄이 붙어 한 덩어리로 읽힌다. */}
|
|
<ul className="flex flex-wrap items-center gap-x-6 gap-y-1 lg:block">
|
|
{menu.map((item, index) => (
|
|
<li key={item.label}>
|
|
<a
|
|
href={item.href}
|
|
/* ★ 스크롤 위치를 따라가며 표시를 옮기지 않는다. 그건 자바스크립트가
|
|
떠야 도는데, 이 사이트는 크롤러가 보는 HTML 이 정본이다.
|
|
문서를 연 시점의 위치(맨 위)만 표시한다. */
|
|
aria-current={index === 0 ? 'true' : undefined}
|
|
className={`tap inline-flex items-center text-[length:var(--fs-sm)] transition-opacity hover:opacity-100 ${
|
|
index === 0 ? '' : 'text-muted'
|
|
}`}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</nav>
|
|
|
|
{booking && (
|
|
<a
|
|
href={booking.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="tap mt-2 inline-flex items-center text-[length:var(--fs-sm)] underline underline-offset-[6px]"
|
|
style={{color: 'var(--color-brand)'}}
|
|
>
|
|
{bookingActionLabel(booking)}
|
|
</a>
|
|
)}
|
|
</header>
|
|
|
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
{/* 유틸 띠 — 위아래 실선 사이에 짧은 문구 셋, 오른쪽 끝에 아주 작은 슬로건. */}
|
|
{(strip.length > 0 || slogan) && (
|
|
<div className="border-line flex flex-col gap-3 border-y py-4 sm:flex-row sm:items-center sm:justify-between sm:gap-6">
|
|
{strip.length > 0 && (
|
|
<ul className="text-muted flex min-w-0 flex-wrap items-center gap-x-4 gap-y-1 text-[length:var(--fs-xs)]">
|
|
{strip.map((text, index) => (
|
|
<li key={text} className="flex items-center gap-4">
|
|
{/* 구분선은 항목 사이에만. 첫 항목 앞에 세로줄이 서면 띠가 잘려 보인다. */}
|
|
{index > 0 && (
|
|
<span aria-hidden className="opacity-40">
|
|
|
|
|
</span>
|
|
)}
|
|
<span>{text}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
{slogan && (
|
|
<p className="text-muted measure shrink-0 text-[length:var(--fs-xs)] leading-[1.9] sm:max-w-[18rem] sm:text-right">
|
|
<HeroCatchphrase>{slogan}</HeroCatchphrase>
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{children}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 하단 — 아주 작은 글씨 3열. 어두운 면을 깔지 않는다(이 안에는 색 띠가 없다). */}
|
|
<footer className="border-line mt-auto w-full border-t">
|
|
<div className="mx-auto grid w-full max-w-[72rem] grid-cols-1 gap-8 px-5 pb-28 pt-10 text-[length:var(--fs-xs)] leading-[1.9] sm:grid-cols-3 lg:px-10 lg:pb-14">
|
|
<p>[{place.name}]</p>
|
|
|
|
{channels.length > 0 ? (
|
|
<ul className="space-y-1">
|
|
{channels.map((link) => (
|
|
<li key={link.url}>
|
|
<a
|
|
href={link.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="underline underline-offset-[5px]"
|
|
>
|
|
{/* 채널 코드 → 사람이 읽는 이름. 수집된 링크 제목은 상호가 붙어 있어 쓰지 않는다. */}
|
|
{bookingLabel(link)}
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<span />
|
|
)}
|
|
|
|
<div className="text-muted space-y-1 sm:text-right">
|
|
{address && <p>{address}</p>}
|
|
{place.phone && (
|
|
<p>
|
|
<a href={`tel:${place.phone}`} className="underline underline-offset-[5px]">
|
|
{place.phone}
|
|
</a>
|
|
</p>
|
|
)}
|
|
{place.email && <p>{place.email}</p>}
|
|
{place.legal?.representative && <p>대표 {place.legal.representative}</p>}
|
|
{place.legal?.businessRegistrationNumber && (
|
|
<p>사업자등록번호 {place.legal.businessRegistrationNumber}</p>
|
|
)}
|
|
{place.legal?.mailOrderNumber && <p>통신판매업신고 {place.legal.mailOrderNumber}</p>}
|
|
{place.legal?.licenseNumber && (
|
|
<p>
|
|
{place.legal.licenseLabel ?? '인허가번호'} {place.legal.licenseNumber}
|
|
</p>
|
|
)}
|
|
{/* ★ 작성자·최종 업데이트는 안마다 같은 모양이어야 한다 (2026-09-04, AEO 진단 0/3)
|
|
한글 날짜("2026년 9월 4일")는 최신성을 재는 쪽이 못 읽는다. 화면 글자도 ISO 로 적고,
|
|
byline 은 E-E-A-T 채점이 본문에서 찾는 값이라 같이 남긴다. 공용 꼬리(SiteFooter)와 한 벌. */}
|
|
<p className="byline">
|
|
<span className="author">작성·운영 {place.name}</span>
|
|
{' · 최종 업데이트: '}
|
|
<time dateTime={payload.site.updatedAt}>{isoDate(payload.site.updatedAt)}</time>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<MobileTabBar />
|
|
</div>
|
|
);
|
|
}
|