lunaron-tools / main.js
Lunaron's picture
Upload 15 files
9345167 verified
Raw History Blame Contribute Delete
18.8 kB
/* ========================================
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 = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`;
} else {
favs.push(toolId);
btnEl.classList.add('active');
btnEl.innerHTML = `<svg viewBox="0 0 24 24" fill="#f59e0b" stroke="#f59e0b" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`;
}
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 ? `<svg viewBox="0 0 24 24" fill="#f59e0b" stroke="#f59e0b" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>` : `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`;
}
}
});
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 = `<svg viewBox="0 0 24 24" fill="${favs.includes(toolId) ? '#f59e0b' : 'none'}" stroke="${favs.includes(toolId) ? '#f59e0b' : 'currentColor'}" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`;
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 = '<div class="moon-loader"></div><div class="moon-loader-text">処理中...</div>';
document.body.appendChild(loader);
}
document.addEventListener('dragover', e => {
e.preventDefault();
});
document.addEventListener('drop', e => {
// 特定の要素やクラスを持つ場合はグローバルな処理を無視する
if (e.target.closest('.drop-zone, .drop-zone-wrap, [type="file"], canvas, textarea, input')) return;
e.preventDefault();
const files = e.dataTransfer.files;
if (files.length > 0) {
const type = files[0].type;
const basePath = window.location.pathname.includes('/tools/') ? '' : 'tools/';
if (type.startsWith('image/')) {
window.location.href = basePath + 'compress.html';
} else if (type.startsWith('video/')) {
window.location.href = basePath + 'video-compress.html';
}
}
});
document.addEventListener('keydown', e => {
if (e.ctrlKey || e.metaKey) {
if (e.key.toLowerCase() === 'k') {
e.preventDefault();
const searchInput = document.getElementById('toolSearchInput');
if (searchInput) {
searchInput.focus();
scrollTo('tools-section');
}
} else if (e.key.toLowerCase() === 's') {
e.preventDefault();
const saveBtn = document.querySelector('[id^="dl-"], .dl-btn, [data-action="save"]');
if (saveBtn) saveBtn.click();
} else if (e.key.toLowerCase() === 'z') {
// Allow default undo for inputs
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
const undoBtn = document.querySelector('[data-action="undo"]');
if (undoBtn) {
e.preventDefault();
undoBtn.click();
}
}
}
}
});
}
/* --- INIT --- */
function init() {
applyTheme(state.theme);
initCards();
initFavorites();
bindEvents();
updateNavUI();
initNavHighlight();
initReveal();
initGlobalUX();
// Handle hash on load (for redirects from tool pages)
if (window.location.hash) {
const id = window.location.hash.substring(1);
setTimeout(() => {
const el = document.getElementById(id);
if (el) scrollTo(id);
}, 500);
}
}
// Global helper for loader
window.LunaronLoader = {
show: () => { const l = $('globalLoading'); if (l) l.classList.add('active'); },
hide: () => { const l = $('globalLoading'); if (l) l.classList.remove('active'); }
};
return { init, updateNavUI };
})();
// Initialize or re-initialize if needed
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', window.LunaronApp.init);
} else {
window.LunaronApp.init();
}