- 네트워크 종속 항목 트리
- 효율적인 캐쉬 수명 사용
- 브라우저 오류가 콘솔에 로그되는 현상 제거
- 메타 설명 추가
- robots.txt 추가
This commit is contained in:
2026-01-14 23:58:23 +09:00
parent 0e3368901a
commit 5db14159d0
4 changed files with 181 additions and 166 deletions

121
script.js
View File

@@ -19,28 +19,47 @@ const dot = document.querySelector('.dot-point');
// ping
async function checkStatus() {
const cards = document.querySelectorAll(".card");
const results = [];
cards.forEach(async (card) => {
for (const card of cards) {
const url = card.getAttribute("data-url");
const dot = card.querySelector(".card__decor.card__decor--tr");
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
// 결과 저장을 위한 약속(Promise) 생성
const checkTask = new Promise((resolve) => {
const img = new Image();
// 5초 타임아웃 설정
const timeoutId = setTimeout(() => {
img.src = "";
resolve({ dot, status: "offline" });
}, 5000);
await fetch(url, {
mode: "no-cors",
cache: "no-cache",
signal: controller.signal,
});
img.onload = () => {
clearTimeout(timeoutId);
resolve({ dot, status: "online" });
};
dot.classList.remove("status-offline");
dot.classList.add("status-online");
clearTimeout(timeoutId);
} catch (e) {
dot.classList.remove("status-online");
dot.classList.add("status-offline");
}
img.onerror = () => {
clearTimeout(timeoutId);
resolve({ dot, status: "offline" }); // 502 에러 시 이쪽으로 빠집니다.
};
// 캐시 방지를 위해 타임스탬프를 붙여서 파비콘 호출
img.src = `${url}/favicon.ico?t=${new Date().getTime()}`;
});
results.push(checkTask);
await new Promise((resolve) => setTimeout(resolve, 100)); // 0.1초 간격 요청
}
// 모든 결과가 모이면 한꺼번에 반영
const finalStatuses = await Promise.all(results);
requestAnimationFrame(() => {
finalStatuses.forEach(({ dot, status }) => {
dot.classList.remove("status-online", "status-offline");
dot.classList.add(status === "online" ? "status-online" : "status-offline");
});
});
}
@@ -57,20 +76,18 @@ function startBoltAction() {
let allBolts = [];
if (TEST_ONLY_SECOND_CARD) {
// 🔧 2번째 카드만 선택 (0-based index)
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("card__decor-acting")
(bolt) => !bolt.classList.contains("is-acting")
);
if (availableBolts.length > 0) {
@@ -82,13 +99,12 @@ function startBoltAction() {
randomBolt.style.setProperty("--r", baseRot);
const BOLT_ANIMATION_DURATION = 16000; // CSS와 반드시 일치
randomBolt.classList.add("is-acting");
randomBolt.classList.add("card__decor-acting");
setTimeout(() => {
randomBolt.classList.remove("card__decor-acting");
}, BOLT_ANIMATION_DURATION);
// setTimeout 대신 이벤트 리스너 사용
randomBolt.addEventListener('animationend', () => {
randomBolt.classList.remove("is-acting");
}, { once: true }); // 메모리 관리를 위해 한 번만 실행 후 제거
}
const nextInterval = Math.random() * 4000 + 3000;
@@ -99,14 +115,16 @@ window.addEventListener("load", () => {
setTimeout(startBoltAction, 2000);
});
// 나사 랜덤 각도 부연`
document.querySelectorAll('.card').forEach((card, cardIndex) => {
card.querySelectorAll('.card__decor').forEach((decor, decorIndex) => {
window.addEventListener('DOMContentLoaded', () => {
// 1. 상태 체크는 1초 뒤에 여유롭게 시작
setTimeout(checkStatus, 1000);
// -30 ~ 150 정도가 나사 느낌 제일 안정적
const angle = Math.floor(Math.random() * 180) - 30;
decor.style.setProperty('--r', `${angle}deg`);
// 2. 나사 랜덤 각도 설정 (DOM이 준비된 직후 실행)
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`);
});
});
});
@@ -161,12 +179,10 @@ window.addEventListener('keydown', (e) => {
});
const logo = document.querySelector('header h1');
// const themes = ['semi-nova', 'nova', 'semi-solaris', 'solaris'];
// const STORAGE_KEY = 'selected-theme';
let startX = 0;
let isDragging = false;
const SWIPE_THRESHOLD = 40; // px
const SWIPE_THRESHOLD = 40;
function getCurrentThemeIndex() {
return themes.findIndex(t => document.body.classList.contains(t));
@@ -177,13 +193,15 @@ function applyTheme(index) {
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');
void dot.offsetWidth; // 애니메이션 리셋
dot.classList.add('blink-alert');
}
const dot = document.querySelector('.dot-point');
if (dot) {
dot.classList.remove('blink-alert');
// 리플로우 유발 코드 제거 (void dot.offsetWidth;)
// 대신 브라우저의 다음 렌더링 사이클을 이용
requestAnimationFrame(() => {
dot.classList.add('blink-alert');
});
}
}
logo.addEventListener('pointerdown', (e) => {
@@ -202,10 +220,8 @@ logo.addEventListener('pointerup', (e) => {
if (index === -1) index = 0;
if (deltaX > 0) {
// 👉 오른쪽
index = (index + 1) % themes.length;
} else {
// 👈 왼쪽
index = (index - 1 + themes.length) % themes.length;
}
@@ -221,19 +237,4 @@ logo.addEventListener('pointercancel', reset);
function reset() {
isDragging = false;
logo.classList.remove('is-sliding');
}
// Focus dot animation
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();
}