287 lines
7.6 KiB
JavaScript
287 lines
7.6 KiB
JavaScript
// 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}`);
|
|
}
|
|
}); |