// Japanese page: header, pinned horizontal hero, floating consultation button.
// Same structure and behaviour as ../hero.jsx; copy from the copy deck (N-*, H-*).
const NAV_ITEMS = [
['サービス', '#solutions'],
['事例', '#work'],
['進め方', '#process'],
['選ばれる理由', '#why'],
['よくある質問', '#faq'],
];
function Header() {
const [open, setOpen] = React.useState(false);
const headerRef = React.useRef(null);
const lastY = React.useRef(0);
const menuBtnRef = React.useRef(null);
useScrollFrame(() => {
const el = headerRef.current;
if (!el) return;
const y = window.scrollY;
el.classList.toggle('is-solid', y > 24);
const goingDown = y > lastY.current + 4;
const goingUp = y < lastY.current - 4;
if (goingDown && y > 240) el.classList.add('is-hidden');
if (goingUp || y < 240) el.classList.remove('is-hidden');
lastY.current = y;
});
// is-solid / is-hidden are toggled imperatively above, so is-open is too:
// a React-managed className would wipe them on every menu toggle.
React.useLayoutEffect(() => {
const el = headerRef.current;
if (!el) return;
el.classList.toggle('is-open', open);
if (open) el.classList.remove('is-hidden');
}, [open]);
React.useEffect(() => {
document.body.classList.toggle('menu-open', open);
if (!open) return undefined;
const onKey = (e) => {
if (e.key === 'Escape') {
setOpen(false);
menuBtnRef.current && menuBtnRef.current.focus();
}
};
const onResize = () => window.innerWidth > 1023 && setOpen(false);
window.addEventListener('keydown', onKey);
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('keydown', onKey);
window.removeEventListener('resize', onResize);
};
}, [open]);
const close = () => setOpen(false);
return (
);
}
// Headline phrases. The pill (a switch whose rising-sun knob slides from off to on) is inserted after PILL_AFTER phrases.
const HERO_WORDS = ['アイデアを、', '現実に。'];
const PILL_AFTER = 1;
function Hero() {
const reduced = useReducedMotion();
const sectionRef = React.useRef(null);
const trackRef = React.useRef(null);
const pillRef = React.useRef(null);
const wordRefs = React.useRef([]);
useScrollFrame(() => {
const section = sectionRef.current;
const track = trackRef.current;
if (!section || !track) return;
if (reduced) {
track.style.transform = '';
if (pillRef.current) pillRef.current.style.removeProperty('--roll');
return;
}
const p = pinProgress(section);
const vw = window.innerWidth;
const distance = Math.max(0, track.scrollWidth - vw);
// Hold briefly at the start, then slide the whole line through.
const t = clamp01((p - 0.04) / 0.86);
track.style.transform = `translate3d(${(-distance * t).toFixed(1)}px,0,0)`;
if (pillRef.current) pillRef.current.style.setProperty('--roll', clamp01((p - 0.02) / 0.26).toFixed(3));
const threshold = vw * 0.78;
wordRefs.current.forEach((w) => {
if (!w) return;
w.classList.toggle('is-on', w.getBoundingClientRect().left < threshold);
});
}, [reduced]);
const parts = [];
HERO_WORDS.forEach((w, i) => {
parts.push( (wordRefs.current[i] = el)} className="hw">{w});
if (i === PILL_AFTER - 1) {
parts.push(
,
);
}
});
return (
{HERO_WORDS.join('')}
{parts}
AIとカスタムソフトウェア開発。業務システム、ダッシュボード、顧客ポータル、業務の自動化、AIツールなどを、4〜8週間で本番稼働まで。
);
}
// Pill that follows the visitor after the hero and gets out of the way near the consultation/CTA/footer.
function FloatingQuote() {
const ref = React.useRef(null);
useScrollFrame(() => {
const el = ref.current;
if (!el) return;
const vh = window.innerHeight;
const inView = (id) => {
const node = document.getElementById(id);
if (!node) return false;
const r = node.getBoundingClientRect();
return r.top < vh && r.bottom > 0;
};
const about = document.getElementById('about');
const pastIntro = about ? about.getBoundingClientRect().bottom < vh * 0.6 : false;
const blocked = ['quote', 'contact', 'footer'].some(inView);
const menuOpen = document.body.classList.contains('menu-open');
el.classList.toggle('is-visible', pastIntro && !blocked && !menuOpen);
// Swap to ink while the button sits over the accent band.
const sol = document.getElementById('solutions');
const r = sol ? sol.getBoundingClientRect() : null;
el.classList.toggle('on-accent', !!r && r.top < vh - 40 && r.bottom > vh - 40);
});
return (
まずは無料見積
);
}
Object.assign(window, { Header, Hero, FloatingQuote });