/* =========================================================================
shared.jsx — site-wide shell: helpers, Header, Footer, PageApp
Loaded on every page before the page script.
========================================================================= */
/* ---------- image asset paths ---------- */
const IMAGE_ASSETS = Object.freeze({
logo: "assets/images/liveons-logo.svg",
heroMain: "assets/images/liveons-hero-main.jpg",
heroPoster: "assets/images/liveons-hero-poster.jpg",
serviceInfluence: "assets/images/liveons-service-influence.jpg",
servicePromotion: "assets/images/liveons-service-promotion.jpg",
serviceSystem: "assets/images/liveons-service-system.jpg",
representativePortrait: "assets/images/liveons-representative-portrait.jpg",
});
/* ---------- video asset paths ---------- */
const VIDEO_ASSETS = Object.freeze({
heroMain: "assets/videos/liveons-hero-main.mp4",
heroMainMobile: "assets/videos/liveons-hero-main-mobile.mp4",
});
/* ---------- tiny helpers ---------- */
const Ph = ({ label, className = "", dark = false, style, src, alt, position = "center" }) => {
const [failed, setFailed] = React.useState(false);
const showImage = !!src && !failed;
return (
{showImage && (

setFailed(true)} />
)}
);
};
const Logo = ({ footer = false }) => (
);
const Arrow = () => (
);
/* ---------- social icons ---------- */
const IconYT = () => ();
const IconIG = () => ();
const IconTT = () => ();
const NAV = [
["事業内容", "services.html"],
["代表メッセージ", "message.html"],
["会社概要", "company.html"],
];
/* ---------- Header ---------- */
function Header({ active = "" }) {
const [scrolled, setScrolled] = React.useState(false);
const [onDark, setOnDark] = React.useState(false);
const [menu, setMenu] = React.useState(false);
React.useEffect(() => {
const hero = document.getElementById("hero-sentinel");
const onScroll = () => {
setScrolled(window.scrollY > 30);
if (hero) setOnDark(hero.dataset.dark === "1" && window.scrollY < window.innerHeight - 120);
else setOnDark(false);
};
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
React.useEffect(() => {
if (!menu) return;
const previous = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = previous;
};
}, [menu]);
const close = () => setMenu(false);
return (
);
}
/* ---------- Footer ---------- */
function Footer() {
const cols = [["事業内容", "services.html"], ["代表メッセージ", "message.html"],
["会社概要", "company.html"], ["お問い合わせ", "contact.html"]];
return (
);
}
/* ---------- shared CTA band ---------- */
function ContactCTA() {
return (
CONTACT
次の挑戦を、一緒に。
事業に関するご相談、取材・協業のお問い合わせはお気軽にどうぞ。担当者よりスピード感を持ってご返信いたします。
);
}
/* ---------- hooks ---------- */
function useReveal(dep) {
React.useEffect(() => {
const els = document.querySelectorAll(".reveal:not(.in)");
if (!("IntersectionObserver" in window)) { els.forEach(e => e.classList.add("in")); return; }
const io = new IntersectionObserver((ents) => {
ents.forEach(en => { if (en.isIntersecting) { en.target.classList.add("in"); io.unobserve(en.target); } });
}, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
els.forEach(e => io.observe(e));
return () => io.disconnect();
}, [dep]);
}
/* ---------- PageApp: wraps a page in shell ---------- */
function PageApp({ active, children }) {
useReveal(true);
return (
<>
{children}
>
);
}
Object.assign(window, {
IMAGE_ASSETS, VIDEO_ASSETS, Ph, Logo, Arrow, IconYT, IconIG, IconTT, NAV,
Header, Footer, ContactCTA, useReveal, PageApp,
});