diff --git a/index.html b/index.html index 4b11a86..30f2f1e 100644 --- a/index.html +++ b/index.html @@ -4,9 +4,18 @@ - - SORI STUDIO - + + + + + + + + + + + sori.studio + @@ -14,129 +23,133 @@

sori.studio

-
+
+
-

Core Infrastructure

-
+ +
-
+
-

Public Services

-
+

Public Services

+ -
+ +
-
+
-

Personal Archive

-
+

Personal Archive

+
- - - -
+ +
+ +
- -
-
Mastodon
-
Sori.Space Mastodon
-
+ +
+
Mastodon
+
Sori.Space Mastodon
+
- -
-
sns.sori.studio
-

소리 스튜디오(Sori Studio)의 공식 마스토돈 인스턴스입니다.
운영자 zenn의 개인적인 기록과 소소한 이야기를 - 공유하는 독립적인 공간입니다.

-

This is the official Mastodon instance of Sori Studio.
It is an - independent space where operator zenn shares his personal records and small stories.

-
-
+ +
+
sns.sori.studio
+

소리 스튜디오(Sori Studio)의 공식 마스토돈 인스턴스입니다.
운영자 zenn의 개인적인 기록과 소소한 이야기를 + 공유하는 독립적인 공간입니다.

+

This is the official Mastodon instance of Sori Studio.
It is an + independent space where operator zenn shares his personal records and small stories.

+
+ -
+
-
- - +
+ + + \ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..14267e9 --- /dev/null +++ b/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / \ No newline at end of file diff --git a/script.js b/script.js index abd70e2..8418e59 100644 --- a/script.js +++ b/script.js @@ -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(); \ No newline at end of file +} \ No newline at end of file diff --git a/style.css b/style.css index bed53cc..bc680c4 100644 --- a/style.css +++ b/style.css @@ -1,5 +1,3 @@ -@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.css"); -@import url("https://fonts.googleapis.com/css2?family=Anton&family=Orbitron:wght@800&family=Staatliches&family=Tenor+Sans&display=swap"); @font-face { font-family: "nova"; src: url("./font/NovaBySoristudio-Regular.otf") format("opentype"); font-weight: normal; font-style: normal; } @font-face { font-family: "solaris"; src: url("./font/Solaris-3-Script.otf") format("opentype"); font-weight: normal; font-style: normal; } @@ -152,7 +150,7 @@ body { -.card__decor-acting::before { animation: bolt-spin-return 16s cubic-bezier(0.65, 0, 0.35, 1) forwards; } + .solaris .section-title { font-size: 1.25rem; } .nova .section-title { font-size: 1.75rem; } @@ -174,7 +172,8 @@ body { .card__decor--br { bottom: 1rem; right: 1rem; } .card__decor::before { content: ""; position: absolute; top: 50%; left: 50%; width: 64%; height: 1.5px; border-radius: 4px; background: #222; transform: translate(-50%, -50%) rotate(var(--r, 0deg)); transition: all 0.3s ease; } -.card__decor.card__decor-acting::before { transform: translate(-50%, -50%) rotate(calc(var(--r, 0deg) + 45deg)); background: #000; } +.card__decor.is-acting::before { transform: translate(-50%, -50%) rotate(calc(var(--r, 0deg) + 45deg)); background: #000; } +.is-acting::before { animation: bolt-spin-return 16s cubic-bezier(0.65, 0, 0.35, 1) forwards; } /* keyframes */