// components.jsx — shared primitives for たつた連泊プランLP
// HP-style: Noto Serif JP ruled, generous whitespace, hairline dividers, muted earth.
function useIsMobile() {
const [isMobile, setIsMobile] = React.useState(window.innerWidth <= 768);
React.useEffect(() => {
const h = () => setIsMobile(window.innerWidth <= 768);
window.addEventListener('resize', h, { passive: true });
return () => window.removeEventListener('resize', h);
}, []);
return isMobile;
}
// Photo element with caption
function Photo({ src, alt, height, ratio, caption, fit = 'cover' }) {
const style = ratio ? { width: '100%', aspectRatio: ratio } : { width: '100%', height };
return (
{caption && (
— {caption}
)}
);
}
// HP-style section heading: English eyebrow + Japanese title, thin hairlines
function SectionHeading({ eyebrow, title, sub, align = 'center', light = false }) {
const ink = light ? '#F7F3E8' : '#2B2620';
const muted = light ? 'rgba(247,243,232,0.55)' : 'rgba(60,52,40,0.55)';
const line = light ? 'rgba(247,243,232,0.4)' : 'rgba(60,52,40,0.35)';
return (
{eyebrow}
{title}
{sub && (
{sub}
)}
);
}
// Top bar — minimal, mix-blend like HP
function TopBar() {
const isMobile = useIsMobile();
const site = window.SITE_INFO || {};
return (
);
}
const navLink = { color: 'inherit', textDecoration: 'none', opacity: 0.9 };
// Floating reserve button
function FloatingCTA() {
const site = window.SITE_INFO || {};
const [on, setOn] = React.useState(false);
React.useEffect(() => {
const h = () => setOn(window.scrollY > 600);
h(); window.addEventListener('scroll', h, { passive: true });
return () => window.removeEventListener('scroll', h);
}, []);
return (
{site.ctaLabel || '連 泊 を 予 約 す る →'}
);
}
Object.assign(window, { Photo, SectionHeading, TopBar, FloatingCTA, useIsMobile });