목업(/s/stay)이 참조하는 스타일시트가 서버에서 사라져(404) 그 화면을 기준으로 삼을 수
없었다. 브라우저 캐시에 남아 있던 규칙을 꺼내 레포에 되돌리고, 섹션 마크업을 목업 HTML
에서 그대로 옮겼다. Playwright 대조로 섹션 래퍼·제목 16/17, 안쪽 구조 14/17 일치.
- index.css: --fs-display~--fs-xs(유동 타이포 7) · --section-space · --color-line/muted ·
.h2 .h3 .panel .paper .measure .border-line .text-muted .divide-line .slider-viewport/track
★ 이 토큰이 없던 동안 컴포넌트가 var(--fs-display)를 써도 브라우저가 조용히 무시했다 —
히어로 제목이 본문 크기로 나오던 원인
- HeroSection: 가운데 → 좌하단. 사진 위에 글자를 한가운데 얹으면 피사체를 정확히 가린다
- AnswerBlock·EssentialInfo·Units·Gallery·Location·Faq·LocalGuide·Weather·About:
목업 마크업 그대로. embla 를 scroll-snap 으로 바꿔 스크립트 없이도 레일이 밀린다
- FestivalSection(계절 탭) · StorySection(이야기 탭 래퍼) · items/TripMap(OSM 타일 지도) 신설
★ 지도는 iframe 이 아니라 타일을 직접 깐다 — iframe 은 핀을 하나밖에 못 찍어
"어떤 순서로 도는가" 를 그릴 수 없다
- items: itinerary·event·video kind 추가(파서에 없어 payload 까지 실려 오고도 화면에서
사라지던 것) · Rail 을 slider-viewport/track 으로 · SongsSection 을 턴테이블로
- color.ts: 선 색을 secondary → mix(bg, text, .2). secondary 는 '본문 다음으로 진한
글자색' 이라 선으로 쓰면 표와 카드가 격자무늬처럼 새까맣게 그어진다
- seo/head.ts: --tpl-texture. 없으면 색만 갱지고 면은 매끈해서 인쇄물로 안 보인다
- seo/verify.ts: unitCode·numberOfRooms 를 화면 대조에서 뺀다
★ unitCode 는 ㎡의 ISO 코드로 priceCurrency 와 같은 성격인데 예외에 없었다. 그래서
room_size 가 있는 사업장은 발행 게이트가 전부 막았다(실측: 가은채 객실 12개).
stay 는 객실 2개 + room_size 없음이라 우연히 통과했다
- shared: LocalPlace.imageUrl/distanceMeters · FestivalEntry.imageUrl ·
ItineraryStop 좌표 · TemplateLook.texture · people/chronicle/postcard imageUrl
★ 좌표·사진은 모델이 만드는 칸이 아니다. 공식 API 로 조회해 채운다
검증: tsc 통과 · Playwright 대조(목업 CSS 주입) 섹션 래퍼·제목 16/17, 안쪽 14/17.
남은 차이는 데이터 한계다 — people 사진은 위키에 원본이 없고(original:false),
weather.note 는 정적 페이지에 날씨 문장을 박는 문제라 보류.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP1ykMnWZFtLFpm2mow1Sw
55 lines
2.1 KiB
TypeScript
55 lines
2.1 KiB
TypeScript
/**
|
|
* 소식 — 사장님이 쓰는 공지·안내.
|
|
*
|
|
* ★ 지역 리서치 아이템과 성격이 다르다. 여기 글의 주인은 사장님이고 출처도 사장님이다 —
|
|
* 그래서 source 를 요구하지 않는다(SourceLine 대신 verified 배지만).
|
|
*/
|
|
import type {EventItem} from '@o2o/shared';
|
|
import {useSite} from '@/lib/site-context';
|
|
import {sectionItems, sectionName} from '@/lib/derive';
|
|
import {ITEM_ACCENT, ITEM_BORDER, ITEM_CARD, ItemSection, tally} from './common';
|
|
|
|
export function EventSection() {
|
|
const payload = useSite();
|
|
const parsed = sectionItems<EventItem>(payload, 'event');
|
|
if (parsed.items.length === 0) return null;
|
|
|
|
return (
|
|
<ItemSection
|
|
id="event"
|
|
name={parsed.title || sectionName(payload, 'event', '소식')}
|
|
subtitle={parsed.subtitle ?? '미리 알아 두시면 좋은 것들'}
|
|
count={tally(parsed.items.length, parsed.unverified, '건')}
|
|
>
|
|
<ul className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
|
{parsed.items.map((notice, index) => (
|
|
<li
|
|
key={`${notice.title}-${index}`}
|
|
className="w4-paper flex flex-col border-2 p-5"
|
|
style={{backgroundColor: ITEM_CARD, borderColor: ITEM_BORDER}}
|
|
>
|
|
{notice.kind && (
|
|
<span
|
|
className="mb-2 self-start border px-2 py-0.5 text-[length:var(--fs-xs)] font-bold"
|
|
style={{borderColor: ITEM_ACCENT, color: ITEM_ACCENT}}
|
|
>
|
|
{notice.kind}
|
|
</span>
|
|
)}
|
|
<h3 className="serif text-[length:var(--fs-lead)] font-bold leading-snug">{notice.title}</h3>
|
|
{notice.summary && (
|
|
<p className="measure mt-2 text-[length:var(--fs-sm)] opacity-80">{notice.summary}</p>
|
|
)}
|
|
{notice.body && (
|
|
// 줄바꿈이 항목 구분이다. 사장님이 쓴 그대로 둔다.
|
|
<p className="mt-3 whitespace-pre-line text-[length:var(--fs-sm)] leading-relaxed opacity-75">
|
|
{notice.body}
|
|
</p>
|
|
)}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</ItemSection>
|
|
);
|
|
}
|