const header = document.querySelector(".site-header"); const toggle = document.querySelector(".menu-toggle"); const nav = document.querySelector(".global-nav"); const pagetop = document.querySelector(".pagetop"); const langSwitch = document.querySelector(".lang-switch"); const LANG_SCROLL_KEY = "shiogama-lang-scroll"; const LANG_POS_PARAM = "langpos"; if ( new URLSearchParams(location.search).has(LANG_POS_PARAM) && "scrollRestoration" in history ) { history.scrollRestoration = "manual"; } function getScrollY() { return ( window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0 ); } function setScrollY(y) { document.documentElement.style.scrollBehavior = "auto"; const scroller = document.scrollingElement || document.documentElement; const max = Math.max(0, scroller.scrollHeight - window.innerHeight); const next = Math.max(0, Math.min(max, y)); try { window.scrollTo({ top: next, left: 0, behavior: "instant" }); } catch (_) { window.scrollTo(0, next); } document.documentElement.scrollTop = next; document.body.scrollTop = next; } function pageBlocks() { return [...document.querySelectorAll(".hero, main > section, .tour-card, .site-footer")]; } function blockKey(el) { if (el.id) return el.id; const name = [ "hero", "tour-lead", "news-wrap", "opening", "about", "higashimono", "tour", "journey", "movie", "partners", "site-footer", ].find((cls) => el.classList.contains(cls)); return name || ""; } function blockTop(el) { return el.getBoundingClientRect().top + getScrollY(); } function captureLangScroll() { const y = getScrollY(); const blocks = pageBlocks(); let current = blocks[0] || null; blocks.forEach((el) => { if (blockTop(el) <= y + 1) current = el; }); const top = current ? blockTop(current) : 0; const height = Math.max(1, current ? current.offsetHeight : 1); return { key: current ? blockKey(current) : "", ratio: (y - top) / height, y, }; } function findBlock(key) { if (!key) return null; return pageBlocks().find((el) => blockKey(el) === key) || null; } function applyLangScroll(data) { const el = findBlock(data.key); if (el && Number.isFinite(Number(data.ratio))) { setScrollY(blockTop(el) + Number(data.ratio) * Math.max(1, el.offsetHeight)); return; } if (Number.isFinite(Number(data.y))) setScrollY(Number(data.y)); } function langDest(href, data) { const file = href.split("#")[0].split("?")[0]; const url = new URL(file, location.href); url.searchParams.set(LANG_POS_PARAM, `${data.key}|${Number(data.ratio).toFixed(5)}`); return `${file}?${url.searchParams.toString()}`; } function readLangPos() { const params = new URLSearchParams(location.search); const langpos = params.get(LANG_POS_PARAM); let data = null; if (langpos) { const sep = langpos.indexOf("|"); data = { key: sep === -1 ? langpos : langpos.slice(0, sep), ratio: sep === -1 ? 0 : Number(langpos.slice(sep + 1)), }; params.delete(LANG_POS_PARAM); const query = params.toString(); history.replaceState(null, "", `${location.pathname}${query ? `?${query}` : ""}`); } if (!data) { try { const raw = sessionStorage.getItem(LANG_SCROLL_KEY); if (raw) data = JSON.parse(raw); } catch (_) {} } try { sessionStorage.removeItem(LANG_SCROLL_KEY); } catch (_) {} return data; } document.querySelectorAll(".lang-switch a").forEach((a) => { a.addEventListener( "click", (e) => { const href = a.getAttribute("href"); if (!href) return; e.preventDefault(); const data = captureLangScroll(); try { sessionStorage.setItem(LANG_SCROLL_KEY, JSON.stringify(data)); } catch (_) {} setTimeout(() => { location.assign(langDest(href, data)); }, 0); }, true, ); }); (function restoreLangScroll() { const data = readLangPos(); if (!data || (!data.key && data.y == null)) return; if ("scrollRestoration" in history) history.scrollRestoration = "manual"; let locked = true; const apply = () => { if (!locked) return; applyLangScroll(data); if (typeof syncLangDock === "function") syncLangDock(); }; const unlock = () => { locked = false; document.documentElement.style.scrollBehavior = ""; }; apply(); requestAnimationFrame(apply); window.addEventListener("pageshow", apply); document.querySelector(".hero-image")?.addEventListener("load", apply); window.addEventListener("wheel", unlock, { passive: true, once: true }); window.addEventListener("touchmove", unlock, { passive: true, once: true }); const until = Date.now() + 1800; const tick = () => { apply(); if (locked && Date.now() < until) requestAnimationFrame(tick); else if (locked) unlock(); }; requestAnimationFrame(tick); window.addEventListener( "load", () => { apply(); requestAnimationFrame(() => { apply(); setTimeout(() => { apply(); unlock(); }, 120); }); }, { once: true }, ); })(); function syncLangDock() { if (!header || !langSwitch) return; const stuck = header.getBoundingClientRect().top <= 1; langSwitch.classList.toggle("is-docked", stuck); } window.addEventListener( "scroll", () => { header.classList.toggle("scrolled", window.scrollY > 40); pagetop?.classList.toggle("is-show", window.scrollY > 400); syncLangDock(); }, { passive: true }, ); window.addEventListener("resize", syncLangDock); syncLangDock(); toggle?.addEventListener("click", () => { const open = toggle.classList.toggle("active"); nav.classList.toggle("open", open); toggle.setAttribute("aria-expanded", String(open)); document.body.style.overflow = open ? "hidden" : ""; }); nav?.querySelectorAll("a").forEach((a) => a.addEventListener("click", () => { toggle?.classList.remove("active"); nav.classList.remove("open"); toggle?.setAttribute("aria-expanded", "false"); document.body.style.overflow = ""; }), ); const io = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add("is-visible"); io.unobserve(entry.target); } }); }, { threshold: 0.12, rootMargin: "0px 0px -40px" }, ); document.querySelectorAll(".reveal").forEach((el) => io.observe(el)); const openingSlides = document.querySelectorAll(".opening-bg span"); if ( openingSlides.length > 1 && !window.matchMedia("(prefers-reduced-motion: reduce)").matches ) { let openingIndex = 0; setInterval(() => { openingSlides[openingIndex].classList.remove("is-active"); openingIndex = (openingIndex + 1) % openingSlides.length; openingSlides[openingIndex].classList.add("is-active"); }, 5000); } document.querySelectorAll("[data-tour-slider]").forEach((root) => { const slides = [...root.querySelectorAll(".tour-slides img")]; if (slides.length < 2) return; const dotsWrap = root.querySelector(".tour-slider-dots"); const label = root.querySelector(".tour-slider-label"); const prev = root.querySelector(".tour-slider-arrow.is-prev"); const next = root.querySelector(".tour-slider-arrow.is-next"); const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; let index = 0; let timer; slides.forEach((img, i) => { const dot = document.createElement("button"); dot.type = "button"; dot.setAttribute("role", "tab"); dot.setAttribute("aria-label", img.dataset.label || `${i + 1}枚目`); dot.addEventListener("click", () => go(i, true)); dotsWrap.append(dot); }); function go(i, user) { index = (i + slides.length) % slides.length; slides.forEach((img, n) => img.classList.toggle("is-active", n === index)); dotsWrap.querySelectorAll("button").forEach((dot, n) => { const active = n === index; dot.classList.toggle("is-active", active); dot.setAttribute("aria-selected", String(active)); }); if (label) label.textContent = slides[index].dataset.label || ""; if (user && timer) start(); } function start() { stop(); if (reduce) return; timer = setInterval(() => go(index + 1), 5000); } function stop() { clearInterval(timer); timer = null; } prev.addEventListener("click", () => go(index - 1, true)); next.addEventListener("click", () => go(index + 1, true)); root.addEventListener("mouseenter", stop); root.addEventListener("mouseleave", start); root.addEventListener("focusin", stop); root.addEventListener("focusout", (e) => { if (!root.contains(e.relatedTarget)) start(); }); go(0); start(); }); const movieGrid = document.querySelector("[data-movie-list]"); if (movieGrid) { const youtubeIdPattern = /^[a-zA-Z0-9_-]{11}$/; const lang = document.documentElement.lang === "en" ? "en" : "ja"; const movieCopy = { ja: { pending: "準備中", empty: "映像は準備中です。", more: "詳細を見る", iframeTitle: "YouTube映像", }, en: { pending: "Coming soon", empty: "Videos are coming soon.", more: "Learn more", iframeTitle: "YouTube video", }, }[lang]; function t(value) { if (value && typeof value === "object" && ("ja" in value || "en" in value)) { return value[lang] || value.ja || value.en || ""; } return typeof value === "string" ? value : ""; } function createMovieCard(item) { const card = document.createElement("article"); card.className = "movie-card reveal"; const embed = document.createElement("div"); embed.className = "movie-embed"; const youtubeId = youtubeIdPattern.test(item.youtubeId || "") ? item.youtubeId : ""; const titleText = t(item.title); if (youtubeId) { const iframe = document.createElement("iframe"); iframe.src = `https://www.youtube-nocookie.com/embed/${youtubeId}?rel=0`; iframe.title = titleText || movieCopy.iframeTitle; iframe.loading = "lazy"; iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"; iframe.allowFullscreen = true; iframe.referrerPolicy = "strict-origin-when-cross-origin"; embed.append(iframe); } else { const pending = document.createElement("div"); pending.className = "movie-pending"; pending.innerHTML = ''; const note = document.createElement("em"); note.textContent = movieCopy.pending; pending.append(note); embed.append(pending); } card.append(embed); const categoryText = t(item.category); if (categoryText) { const meta = document.createElement("p"); meta.className = "movie-meta"; meta.textContent = categoryText; card.append(meta); } if (titleText) { const title = document.createElement("h3"); title.textContent = titleText; card.append(title); } const descriptionText = t(item.description); if (descriptionText) { const desc = document.createElement("p"); desc.className = "movie-desc"; desc.textContent = descriptionText; card.append(desc); } const href = item.link && item.link.href; if (href) { const link = document.createElement("a"); link.className = "movie-link"; link.href = href; link.target = "_blank"; link.rel = "noopener"; link.append( document.createTextNode(t(item.link.label) || movieCopy.more), ); const arrow = document.createElement("span"); arrow.textContent = "↗"; link.append(arrow); card.append(link); } return card; } fetch("data/movies.json?v=20260828j") .then((res) => { if (!res.ok) throw new Error(String(res.status)); return res.json(); }) .then((data) => { const videos = Array.isArray(data) ? data : data.videos; movieGrid.replaceChildren(); if (!videos || !videos.length) { movieGrid.innerHTML = `

${movieCopy.empty}

`; return; } videos.forEach((item) => movieGrid.append(createMovieCard(item))); movieGrid.querySelectorAll(".reveal").forEach((el) => io.observe(el)); }) .catch(() => { movieGrid.innerHTML = `

${movieCopy.empty}

`; }); }