// Focus dot animation const dot = document.querySelector('.dot-point'); function updateDotState() { const isActive = document.visibilityState === 'visible' && document.hasFocus(); dot.classList.toggle('is-active', isActive); } document.addEventListener('visibilitychange', updateDotState); window.addEventListener('focus', updateDotState); window.addEventListener('blur', updateDotState); updateDotState(); // ping async function checkStatus() { const cards = document.querySelectorAll(".card"); const checkTasks = Array.from(cards).map(async (card) => { const url = card.getAttribute("data-url"); const dot = card.querySelector(".card__decor.card__decor--tr"); try { // mode: 'no-cors'를 사용하면 타 도메인의 리소스 존재 여부만 빠르게 확인 가능 const response = await fetch(`${url}/favicon.ico?t=${Date.now()}`, { mode: 'no-cors', signal: AbortSignal.timeout(5000) }); return { dot, status: "online" }; } catch (error) { return { dot, status: "offline" }; } }); const finalStatuses = await Promise.all(checkTasks); requestAnimationFrame(() => { finalStatuses.forEach(({ dot, status }) => { dot.classList.remove("status-online", "status-offline"); dot.classList.add(status === "online" ? "status-online" : "status-offline"); }); }); } checkStatus(); setInterval(checkStatus, 300000); // bolt animation - Test Mode // const TEST_ONLY_SECOND_CARD = true; const TEST_ONLY_SECOND_CARD = false; // bolt animation function startBoltAction() { let allBolts = []; if (TEST_ONLY_SECOND_CARD) { const secondCard = document.querySelectorAll(".card")[1]; if (!secondCard) return; allBolts = secondCard.querySelectorAll(".card__decor"); } else { allBolts = document.querySelectorAll(".card__decor"); } if (allBolts.length === 0) return; const availableBolts = Array.from(allBolts).filter( (bolt) => !bolt.classList.contains("is-acting") ); if (availableBolts.length > 0) { const randomBolt = availableBolts[Math.floor(Math.random() * availableBolts.length)]; const baseRot = randomBolt.style.getPropertyValue("--r") || "0deg"; randomBolt.style.setProperty("--r", baseRot); randomBolt.classList.add("is-acting"); randomBolt.addEventListener('animationend', () => { randomBolt.classList.remove("is-acting"); }, { once: true }); } const nextInterval = Math.random() * 4000 + 3000; setTimeout(startBoltAction, nextInterval); } window.addEventListener("load", () => { setTimeout(startBoltAction, 2000); }); window.addEventListener('DOMContentLoaded', () => { setTimeout(checkStatus, 1000); document.querySelectorAll('.card').forEach((card) => { card.querySelectorAll('.card__decor').forEach((decor) => { const angle = Math.floor(Math.random() * 180) - 30; decor.style.setProperty('--r', `${angle}deg`); }); }); }); // Text Changer const themes = ['semi-nova', 'nova', 'semi-solaris', 'solaris']; const STORAGE_KEY = 'selected-theme'; const savedTheme = localStorage.getItem(STORAGE_KEY) || themes[0]; document.body.classList.add(savedTheme); window.addEventListener('keydown', (e) => { const isMac = navigator.platform.toUpperCase().includes('MAC'); const isModifierPressed = isMac ? e.metaKey : e.ctrlKey; const isLeft = e.code === 'ArrowLeft'; const isRight = e.code === 'ArrowRight'; if ( isModifierPressed && (isLeft || isRight) && !['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName) && !document.activeElement?.isContentEditable ) { e.preventDefault(); let currentIndex = themes.findIndex(t => document.body.classList.contains(t)); if (currentIndex === -1) currentIndex = 0; if (isRight) { currentIndex = (currentIndex + 1) % themes.length; } else if (isLeft) { currentIndex = (currentIndex - 1 + themes.length) % themes.length; } const nextTheme = themes[currentIndex]; themes.forEach(t => document.body.classList.remove(t)); document.body.classList.add(nextTheme); localStorage.setItem(STORAGE_KEY, nextTheme); console.log(`Current Theme: ${nextTheme}`); } }); // Text Changer - Swipe Gesture const logo = document.querySelector('header h1'); let startX = 0; let isDragging = false; const SWIPE_THRESHOLD = 40; function getCurrentThemeIndex() { return themes.findIndex(t => document.body.classList.contains(t)); } function applyTheme(index) { themes.forEach(t => document.body.classList.remove(t)); document.body.classList.add(themes[index]); localStorage.setItem(STORAGE_KEY, themes[index]); const dot = document.querySelector('.dot-point'); if (dot) { dot.classList.remove('blink-alert'); requestAnimationFrame(() => { dot.classList.add('blink-alert'); }); } } logo.addEventListener('pointerdown', (e) => { startX = e.clientX; isDragging = true; logo.classList.add('is-sliding'); }); logo.addEventListener('pointerup', (e) => { if (!isDragging) return; const deltaX = e.clientX - startX; if (Math.abs(deltaX) > SWIPE_THRESHOLD) { let index = getCurrentThemeIndex(); if (index === -1) index = 0; if (deltaX > 0) { index = (index + 1) % themes.length; } else { index = (index - 1 + themes.length) % themes.length; } applyTheme(index); } reset(); }); logo.addEventListener('pointerleave', reset); logo.addEventListener('pointercancel', reset); function reset() { isDragging = false; logo.classList.remove('is-sliding'); } // Theme Toggle Button const THEME_STORAGE_KEY = 'color-theme'; const themeWrapper = document.querySelector('.theme-toggle-wrapper'); const themeToggleBtn = document.querySelector('.theme-toggle-btn'); const themeOptions = document.querySelectorAll('.theme-option'); function initTheme() { const savedTheme = localStorage.getItem(THEME_STORAGE_KEY) || 'dark'; applyColorTheme(savedTheme); updateActiveState(savedTheme); } function applyColorTheme(theme) { if (theme === 'light') { document.documentElement.classList.add('light-mode'); } else { document.documentElement.classList.remove('light-mode'); } localStorage.setItem(THEME_STORAGE_KEY, theme); } function updateActiveState(theme) { themeOptions.forEach(option => { if (option.dataset.theme === theme) { option.classList.add('is-active'); } else { option.classList.remove('is-active'); } }); } themeToggleBtn.addEventListener('click', (e) => { e.stopPropagation(); themeWrapper.classList.toggle('is-open'); }); themeOptions.forEach(option => { option.addEventListener('click', (e) => { e.stopPropagation(); const selectedTheme = option.dataset.theme; applyColorTheme(selectedTheme); updateActiveState(selectedTheme); themeWrapper.classList.remove('is-open'); }); }); document.addEventListener('click', () => { themeWrapper.classList.remove('is-open'); }); initTheme(); // Theme toggle - Keyboard Shortcut window.addEventListener('keydown', (e) => { const isMac = navigator.platform.toUpperCase().includes('MAC'); const isModifierPressed = isMac ? e.metaKey : e.ctrlKey; const isUp = e.code === 'ArrowUp'; const isDown = e.code === 'ArrowDown'; if ( isModifierPressed && (isUp || isDown) && !['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName) && !document.activeElement?.isContentEditable ) { e.preventDefault(); const currentTheme = localStorage.getItem(THEME_STORAGE_KEY) || 'dark'; const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; applyColorTheme(newTheme); updateActiveState(newTheme); console.log(`Color Theme: ${newTheme}`); } });