NeonFlux
========================================================================
-->
Premium Sponsor Link
`;
}
});
feed.innerHTML = containerHtml;
}
function renderPagination() {
let html = '';
// 「前へ」ボタン
html += ``;
// 動的ページネーションロジック (現在ページを中心に前後10ページを動的にシフト)
const maxVisiblePages = 10;
let startPage = 1;
let endPage = 10;
if (currentPage > 6) {
startPage = currentPage - 5;
endPage = currentPage + 4;
}
for (let i = startPage; i <= endPage; i++) {
html += ``;
}
// 「次へ」ボタン (制限なしでどこまでも進めるようにNextを常に有効化)
html += ``;
pagination.innerHTML = html;
}
function changePage(targetPage) {
performSearch(currentQuery, targetPage);
// ページ切替時に上部へスムーズスクロール
window.scrollTo({top: 0, behavior: 'smooth'});
}
// HTMLエスケープ処理(セキュリティ対策)
function escapeHtml(str) {
return str
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// --- シアターモード制御 ---
let isHistoryPushed = false;
function openTheater(embedUrl, title, thumbUrl = '', duration = '') {
currentPlayingVideo = {
embed_url: embedUrl,
title: title,
thumb_url: thumbUrl,
duration: duration
};
const modalBtn = document.getElementById('modal-fav-trigger');
if (modalBtn) {
modalBtn.setAttribute('data-url', embedUrl);
}
// XVideoの埋め込みiframeは以下の設定でロード
// allowにfullscreenを追加、sandboxを適用し、端末での全画面再生を許可
iframeWrapper.innerHTML = `
`;
// 再生中動画タイトルをモーダル下に表示
document.getElementById('modal-title').innerText = title;
// 関連動画カルーセルを描画
renderRelatedVideos(embedUrl);
// ハートマークのUI状態同期
updateHeartUI(embedUrl);
theaterModal.classList.add('active');
// スクロール固定
document.body.style.overflow = 'hidden';
// スマホの「戻る」ジェスチャーやブラウザバック連動ハック
history.pushState({ theaterOpen: true }, '');
isHistoryPushed = true;
}
// 関連動画カード描画ロジック
function renderRelatedVideos(currentEmbedUrl) {
const container = document.getElementById('related-container');
container.innerHTML = '';
// 現在再生中の動画以外の動画リストをフィルタリング
const filtered = loadedVideos.filter(v => v.embed_url !== currentEmbedUrl);
// シャッフルして最大15件を表示(関連動画の増量)
const shuffled = filtered.sort(() => 0.5 - Math.random());
const selected = shuffled.slice(0, 15);
let html = '';
selected.forEach(video => {
html += `
`;
});
container.innerHTML = html;
}
// 関連動画タップ時にモーダルを開いたまま動画と関連動画一覧をシームレスに切り替える
function switchVideo(embedUrl, title) {
// loadedVideos からサムネイルと尺情報を検索
const matched = loadedVideos.find(v => v.embed_url === embedUrl);
const thumbUrl = matched ? matched.thumb_url : '';
const duration = matched ? matched.duration : '';
currentPlayingVideo = {
embed_url: embedUrl,
title: title,
thumb_url: thumbUrl,
duration: duration
};
const modalBtn = document.getElementById('modal-fav-trigger');
if (modalBtn) {
modalBtn.setAttribute('data-url', embedUrl);
}
const iframe = iframeWrapper.querySelector('iframe');
if (iframe) {
iframe.src = embedUrl;
}
// タイトルと関連動画を更新
document.getElementById('modal-title').innerText = title;
renderRelatedVideos(embedUrl);
// ハート状態同期
updateHeartUI(embedUrl);
// モーダルのスクロールを上部にリセット
document.querySelector('.modal-content').scrollTo({top: 0, behavior: 'smooth'});
}
// スマホ疑似フルスクリーン切り替え関数
function toggleCssFullscreen() {
const content = document.querySelector('.modal-content');
content.classList.toggle('fullscreen-mode');
// モーダル背景も真っ黒にトグルする
theaterModal.classList.toggle('has-fullscreen');
const btn = document.getElementById('modal-fs-btn');
if (content.classList.contains('fullscreen-mode')) {
btn.innerHTML = '';
} else {
btn.innerHTML = '';
}
}
function closeTheater(shouldGoBack = true) {
// モーダルを閉じる際に疑似フルスクリーン状態をリセット
const content = document.querySelector('.modal-content');
content.classList.remove('fullscreen-mode');
theaterModal.classList.remove('has-fullscreen');
const btn = document.getElementById('modal-fs-btn');
if (btn) {
btn.innerHTML = '';
}
theaterModal.classList.remove('active');
iframeWrapper.innerHTML = ''; // 再生停止のためにiframeを破棄
// スクロール復元
document.body.style.overflow = '';
// popstateからではなく、手動(画面のバツボタンなど)で閉じた場合は、追加したダミー履歴を消すために戻る
if (shouldGoBack && isHistoryPushed) {
history.back();
}
isHistoryPushed = false;
}
// スマホの「戻る」操作(スワイプや物理キー)の検知時にモーダルを閉じる
window.addEventListener('popstate', (event) => {
if (theaterModal.classList.contains('active')) {
closeTheater(false); // すでにpopstateで戻っているのでhistory.backは不要
}
});
// モーダルプレイヤー外の暗い余白部分をタップしても閉じるようにする
theaterModal.addEventListener('click', (event) => {
// 疑似全画面モード中は誤タップで閉じてしまわないようにガード
const content = document.querySelector('.modal-content');
if (content && content.classList.contains('fullscreen-mode')) {
return;
}
if (event.target === theaterModal) {
closeTheater();
}
});
// --- 証拠隠滅 パニックボタン機能 ---
function triggerPanic() {
// ブラウザの戻るキーでも戻れないように履歴を完全に上書きして健全なサイトへ即座にリダイレクト
window.location.replace("https://typhoon.yahoo.co.jp/weather/");
}
// --- PWA サービスワーカー登録とインストールバナー ---
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/static/sw.js')
.then(reg => console.log('ServiceWorker registered:', reg.scope))
.catch(err => console.log('ServiceWorker registration failed:', err));
});
}
// インストールプロンプトのハンドリング
let deferredPrompt;
const pwaBanner = document.getElementById('pwa-banner');
const pwaInstallBtn = document.getElementById('pwa-install-btn');
window.addEventListener('beforeinstallprompt', (e) => {
// ブラウザ標準のプロンプトを抑制
e.preventDefault();
deferredPrompt = e;
// 独自の極上インストールバナーを表示
pwaBanner.classList.add('show');
});
pwaInstallBtn.addEventListener('click', async () => {
if (deferredPrompt) {
pwaBanner.classList.remove('show');
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(`User response to install prompt: ${outcome}`);
deferredPrompt = null;
}
});
// --- トップへ戻る機能 ---
function goHome() {
currentQuery = userLang === 'ja' ? 'OL' : 'beautiful';
performSearch(currentQuery, 1, false); // トップへ戻る際も検索欄は空にする
window.scrollTo({top: 0, behavior: 'smooth'});
}
// --- サイドメニュー(ドロワー)開閉トグル ---
function toggleMenu() {
const drawer = document.getElementById('drawer-menu');
const overlay = document.getElementById('menu-overlay');
drawer.classList.toggle('active');
overlay.classList.toggle('active');
}
// --- カテゴリー選択と自動検索 ---
function selectCategory(categoryName) {
toggleMenu(); // メニューを閉じる
performSearch(categoryName, 1, true); // カテゴリー選択時は入力欄に値を書き込む
}
// ==========================================
// 【お気に入り動画保存ロジック (localStorage)】
// ==========================================
function getFavorites() {
const favs = localStorage.getItem('neonflux_favorites');
return favs ? JSON.parse(favs) : [];
}
function isFavorite(embedUrl) {
const favs = getFavorites();
return favs.some(v => v.embed_url === embedUrl);
}
function toggleFavorite(video, event) {
if (event) {
event.stopPropagation(); // 親要素のonclick(動画再生モーダル起動)をブロック
}
let favs = getFavorites();
const index = favs.findIndex(v => v.embed_url === video.embed_url);
if (index > -1) {
// 登録解除
favs.splice(index, 1);
} else {
// 新規登録
favs.push(video);
}
localStorage.setItem('neonflux_favorites', JSON.stringify(favs));
// ハートUIの点灯状態を同期更新
updateHeartUI(video.embed_url);
}
function updateHeartUI(embedUrl) {
const active = isFavorite(embedUrl);
// 1. フィードのハートボタンを更新
const buttons = document.querySelectorAll(`.fav-btn[data-url="${embedUrl}"]`);
buttons.forEach(btn => {
if (active) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
// 2. モーダル内のハートボタンを更新
const modalBtn = document.getElementById('modal-fav-trigger');
if (modalBtn && modalBtn.getAttribute('data-url') === embedUrl) {
if (active) {
modalBtn.classList.add('active');
} else {
modalBtn.classList.remove('active');
}
}
// 3. お気に入り画面表示中の場合、リアルタイムでフィードをリフレッシュ
if (currentQuery === 'favorites_list') {
showFavorites(false); // メニューは閉じずに再描画
}
}
function toggleModalFavorite() {
if (currentPlayingVideo) {
toggleFavorite(currentPlayingVideo);
}
}
function showFavorites(shouldCloseMenu = true) {
if (shouldCloseMenu) {
toggleMenu();
}
currentQuery = 'favorites_list';
currentPage = 1;
feed.innerHTML = '';
pagination.innerHTML = '';
const favs = getFavorites();
if (favs.length === 0) {
showState('info', 'お気に入り動画がありません。動画カードやプレイヤーのハートマークを押して保存してください。');
return;
}
hideState();
// 関連動画の抽出元として保持
loadedVideos = favs;
renderFeed(favs);
}