/* ======================================== LUNARON TOOLS — JavaScript ======================================== */ window.LunaronApp = (() => { 'use strict'; /* --- STATE --- */ const state = { theme: localStorage.getItem('lunaronTheme') || 'light', isLoggedIn: localStorage.getItem('lunaron_isLoggedIn') === 'true', currentUser: JSON.parse(localStorage.getItem('lunaron_user') || 'null'), activeTab: 'login', }; /* --- ELEMENTS (resolved lazily) --- */ const $ = id => document.getElementById(id); const $$ = sel => document.querySelectorAll(sel); /* --- THEME --- */ function applyTheme(theme) { document.documentElement.setAttribute('data-theme', theme); state.theme = theme; localStorage.setItem('lunaronTheme', theme); } /* --- FILTER --- */ function setFilter(filter) { $$('.filter-btn').forEach(btn => btn.classList.toggle('active', btn.dataset.filter === filter) ); $$('.category-section').forEach(sec => { if(sec.id !== 'fav-section') sec.style.display = (filter === 'all' || sec.dataset.category === filter) ? '' : 'none'; }); } /* --- SEARCH --- */ function handleSearch(e) { const term = e.target.value.toLowerCase(); if (term.length > 0) { $$('.filter-btn').forEach(btn => btn.classList.toggle('active', btn.dataset.filter === 'all')); $$('.category-section').forEach(sec => { if(sec.id !== 'fav-section') sec.style.display = ''; }); } else { setFilter('all'); } $$('.category-section:not(#fav-section) .tool-card').forEach(card => { const name = card.querySelector('.tool-name')?.textContent.toLowerCase() || ''; const desc = card.querySelector('.tool-desc')?.textContent.toLowerCase() || ''; if (name.includes(term) || desc.includes(term)) { card.style.display = ''; card.style.animation = 'none'; card.style.opacity = '1'; } else { card.style.display = 'none'; } }); $$('.category-section:not(#fav-section)').forEach(sec => { const visibleCards = sec.querySelectorAll('.tool-card[style*="display: none"]'); const totalCards = sec.querySelectorAll('.tool-card'); if (term.length > 0) { sec.style.display = visibleCards.length === totalCards.length ? 'none' : ''; } }); } /* --- CARD STAGGER ANIMATION --- */ function initCards() { $$('.tool-card').forEach((card, i) => { card.style.animationDelay = (i * 0.04) + 's'; }); } /* --- FAVORITES --- */ function toggleFavorite(toolId, btnEl) { let favs = JSON.parse(localStorage.getItem('lunaron_favs') || '[]'); const isFav = favs.includes(toolId); if (isFav) { favs = favs.filter(id => id !== toolId); btnEl.classList.remove('active'); btnEl.innerHTML = ``; } else { favs.push(toolId); btnEl.classList.add('active'); btnEl.innerHTML = ``; } localStorage.setItem('lunaron_favs', JSON.stringify(favs)); $$('.tool-card').forEach(card => { if(card.dataset.toolId === toolId) { const b = card.querySelector('.fav-btn'); if(b && b !== btnEl) { b.classList.toggle('active', !isFav); b.innerHTML = !isFav ? `` : ``; } } }); renderFavorites(); } function renderFavorites() { const favSection = $('fav-section'); const favGrid = $('fav-grid'); if (!favSection || !favGrid) return; let favs = JSON.parse(localStorage.getItem('lunaron_favs') || '[]'); if (favs.length === 0) { favSection.style.display = 'none'; return; } favSection.style.display = ''; favGrid.innerHTML = ''; favs.forEach(toolId => { const originalCard = document.querySelector(`.category-section:not(#fav-section) .tool-card[data-tool-id="${toolId}"]`); if (originalCard) { const clone = originalCard.cloneNode(true); const btn = clone.querySelector('.fav-btn'); if (btn) { btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); toggleFavorite(toolId, btn); }); } favGrid.appendChild(clone); } }); } function initFavorites() { if (!window.location.pathname.endsWith('index.html') && !window.location.pathname.endsWith('/')) return; let favs = JSON.parse(localStorage.getItem('lunaron_favs') || '[]'); $$('.category-section:not(#fav-section) .tool-card').forEach(card => { const toolId = card.getAttribute('href'); card.dataset.toolId = toolId; const starBtn = document.createElement('button'); starBtn.className = `fav-btn ${favs.includes(toolId) ? 'active' : ''}`; starBtn.innerHTML = ``; starBtn.style.cssText = 'position: absolute; top: 16px; right: 16px; background: var(--bg-glass); border: 1px solid var(--border-glass); border-radius: 50%; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 10; transition: all 0.2s; backdrop-filter: blur(4px); color: var(--text-primary);'; starBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); toggleFavorite(toolId, starBtn); }); starBtn.addEventListener('mouseenter', () => { starBtn.style.transform = 'scale(1.1)'; }); starBtn.addEventListener('mouseleave', () => { starBtn.style.transform = 'scale(1)'; }); card.appendChild(starBtn); }); renderFavorites(); } /* --- MODAL --- */ function openModal(tab) { const overlay = $('authOverlay'); if (overlay) { overlay.classList.add('open'); document.body.style.overflow = 'hidden'; } setTab(tab || 'login'); } function closeModal() { const overlay = $('authOverlay'); if (overlay) { overlay.classList.remove('open'); document.body.style.overflow = ''; } } function setTab(tab) { state.activeTab = tab; $$('.modal-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab)); const lf = $('loginForm'), sf = $('signupForm'); if (lf) lf.style.display = tab === 'login' ? '' : 'none'; if (sf) sf.style.display = tab === 'signup' ? '' : 'none'; } /* --- ACCOUNT MENU --- */ function openAccountMenu(e) { e && e.stopPropagation(); $('accountMenu') && $('accountMenu').classList.toggle('open'); } function closeAccountMenu() { $('accountMenu') && $('accountMenu').classList.remove('open'); } /* --- AUTH --- */ function loginUser(user) { state.isLoggedIn = true; state.currentUser = user; localStorage.setItem('lunaron_isLoggedIn', 'true'); localStorage.setItem('lunaron_user', JSON.stringify(user)); updateNavUI(); closeModal(); } function logout() { state.isLoggedIn = false; state.currentUser = null; localStorage.removeItem('lunaron_isLoggedIn'); localStorage.removeItem('lunaron_user'); closeAccountMenu(); updateNavUI(); } function updateNavUI() { const nl = $('navLogin'), ns = $('navSignup'), na = $('navAvatar'); const ab = $('avatarBtn'), an = $('accountName'), ae = $('accountEmail'), ap = $('accountPlan'); if (state.isLoggedIn && state.currentUser) { if (nl) nl.style.display = 'none'; if (ns) ns.style.display = 'none'; if (na) na.style.display = ''; if (ab) ab.textContent = (state.currentUser.name || '?')[0].toUpperCase(); if (an) an.textContent = state.currentUser.name; if (ae) ae.textContent = state.currentUser.email; if (ap) { const isPro = state.currentUser.plan === 'pro'; ap.textContent = isPro ? 'Pro Plan' : 'Free Plan'; ap.className = 'account-plan ' + (isPro ? 'pro-plan' : 'free-plan'); } } else { if (nl) nl.style.display = ''; if (ns) ns.style.display = ''; if (na) na.style.display = 'none'; } } /* --- SCROLL --- */ function scrollTo(id) { const el = document.getElementById(id); if (el) { window.scrollTo({ top: el.getBoundingClientRect().top + scrollY - 80, behavior: 'smooth' }); } else { // Not on home page, redirect window.location.href = (window.location.pathname.includes('/tools/') ? '../index.html' : './index.html') + '#' + id; } } /* --- NAV HIGHLIGHT --- */ function initNavHighlight() { const links = $$('.nav-link[data-section]'); if (!links.length) return; const obs = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { links.forEach(l => l.classList.toggle('active', l.dataset.section === e.target.id)); } }); }, { rootMargin: '-35% 0px -35% 0px' }); links.forEach(l => { const s = document.getElementById(l.dataset.section); if (s) obs.observe(s); }); } /* --- SCROLL REVEAL --- */ function initReveal() { $$('.category-section, .pricing-section, .tool-page > div').forEach(el => { el.classList.add('reveal'); }); const reveals = $$('.reveal'); if (!reveals.length) return; const observer = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('active'); observer.unobserve(e.target); } }); }, { rootMargin: '0px 0px -10% 0px' }); reveals.forEach(r => observer.observe(r)); } /* --- EVENT BINDING (Event Delegation for robustness) --- */ function bindEvents() { // Delegation for nav/global actions document.addEventListener('click', e => { // Theme const themeBtn = e.target.closest('#themeToggle'); if (themeBtn) applyTheme(state.theme === 'dark' ? 'light' : 'dark'); // Modal open const loginBtn = e.target.closest('[data-action="open-login"]'); if (loginBtn) openModal('login'); const signupBtn = e.target.closest('[data-action="open-signup"]'); if (signupBtn) openModal('signup'); // Scroll to pricing const priceBtn = e.target.closest('[data-action="scroll-pricing"]'); if (priceBtn) scrollTo('pricing'); // Nav links (section scroll) const navLink = e.target.closest('.nav-link[data-section]'); if (navLink) scrollTo(navLink.dataset.section); // Account menu const avatarBtn = e.target.closest('#avatarBtn'); if (avatarBtn) openAccountMenu(e); const logoutBtn = e.target.closest('#logoutBtn'); if (logoutBtn) logout(); // Modal tabs const modalTab = e.target.closest('.modal-tab'); if (modalTab) setTab(modalTab.dataset.tab); // Switch tab links const switchLink = e.target.closest('[data-switch-tab]'); if (switchLink) { e.preventDefault(); setTab(switchLink.dataset.switchTab); } // Close modal on overlay click if (e.target === $('authOverlay')) closeModal(); // Close account menu on outside click const menu = $('accountMenu'); const abtn = $('avatarBtn'); if (menu && abtn && !menu.contains(e.target) && e.target !== abtn) closeAccountMenu(); }); // Filters (usually static) $$('.filter-btn').forEach(btn => btn.addEventListener('click', () => setFilter(btn.dataset.filter))); // Search const searchInput = $('toolSearchInput'); if (searchInput) searchInput.addEventListener('input', handleSearch); // Modal close (static) $('authClose') && $('authClose').addEventListener('click', closeModal); // Keyboard document.addEventListener('keydown', e => { if (e.key === 'Escape') { closeModal(); closeAccountMenu(); } }); // Forms — Supabase 認証 document.addEventListener('submit', async e => { const auth = window.LunaronAuth; // ログインフォーム if (e.target.id === 'loginFormEl') { e.preventDefault(); const email = e.target.querySelector('[name="email"]').value.trim(); const password = e.target.querySelector('[name="password"]')?.value || ''; const btn = e.target.querySelector('button[type="submit"]'); if (btn) { btn.disabled = true; btn.textContent = '認証中...'; } if (auth) { const { data, error } = await auth.signIn(email, password); if (error) { alert('ログインに失敗しました: ' + (error.message || '入力内容を確認してください')); } // onAuthStateChange が UI を更新する } else { // Supabase未設定時はフォールバック loginUser({ name: email.split('@')[0], email, plan: 'free' }); } if (btn) { btn.disabled = false; btn.textContent = 'ログイン'; } } // 新規登録フォーム if (e.target.id === 'signupFormEl') { e.preventDefault(); const name = e.target.querySelector('[name="name"]').value.trim(); const email = e.target.querySelector('[name="email"]').value.trim(); const password = e.target.querySelector('[name="password"]')?.value || ''; const btn = e.target.querySelector('button[type="submit"]'); if (btn) { btn.disabled = true; btn.textContent = '登録中...'; } if (auth) { const { data, error } = await auth.signUp(email, password, name); if (error) { alert('登録に失敗しました: ' + (error.message || '入力内容を確認してください')); } else { alert('確認メールを送信しました。メールをご確認ください。'); closeModal(); } } else { loginUser({ name, email, plan: 'free' }); } if (btn) { btn.disabled = false; btn.textContent = '無料で登録する'; } } }); // Google OAuth ボタン document.addEventListener('click', async e => { const googleBtn = e.target.closest('.social-btn'); if (googleBtn) { if (window.LunaronAuth) { googleBtn.disabled = true; googleBtn.textContent = 'Googleに接続中...'; const { error } = await window.LunaronAuth.signInWithGoogle(); if (error) { alert('Google認証に失敗しました。'); googleBtn.disabled = false; } // リダイレクトするので復元不要 } else { alert('認証設定が必要です。.env に SUPABASE_URL と SUPABASE_ANON_KEY を設定してください。'); } } }); // Pricing CTA document.addEventListener('click', e => { const pCta = e.target.closest('[data-action="pricing-cta"]'); if (pCta && !state.isLoggedIn) openModal('signup'); }); } /* --- GLOBAL UX --- */ function initGlobalUX() { if (!document.getElementById('globalLoading')) { const loader = document.createElement('div'); loader.id = 'globalLoading'; loader.className = 'global-loading-overlay'; loader.innerHTML = '