From 55e7c64eceecb9438a34dde62b520150036c1e0e Mon Sep 17 00:00:00 2001 From: zenn Date: Tue, 20 Jan 2026 16:39:14 +0900 Subject: [PATCH] =?UTF-8?q?[260120]=20-=20=EC=83=89=EC=83=81=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EC=88=98=ED=99=94=20-=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=AA=A8=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 71 +++- script.js | 169 ++++++---- style.css | 938 ++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 987 insertions(+), 191 deletions(-) diff --git a/index.html b/index.html index 30f2f1e..89995c3 100644 --- a/index.html +++ b/index.html @@ -1,26 +1,55 @@ - + - - - - - - - - - - sori.studio + + + + + + + + + + + + + + +

sori.studio

+
+ +
+ + +
+
@@ -32,7 +61,9 @@ -
+
@@ -51,7 +82,9 @@ -
+
@@ -70,7 +103,9 @@ -
+
@@ -97,7 +132,9 @@ -
+
@@ -124,7 +161,9 @@ -
+
diff --git a/script.js b/script.js index 8418e59..e05dcc9 100644 --- a/script.js +++ b/script.js @@ -1,60 +1,43 @@ // Focus dot animation const dot = document.querySelector('.dot-point'); - function updateDotState() { - const isActive = - document.visibilityState === 'visible' && - document.hasFocus(); +function updateDotState() { + const isActive = + document.visibilityState === 'visible' && + document.hasFocus(); - dot.classList.toggle('is-active', isActive); - } + dot.classList.toggle('is-active', isActive); +} - document.addEventListener('visibilitychange', updateDotState); - window.addEventListener('focus', updateDotState); - window.addEventListener('blur', updateDotState); +document.addEventListener('visibilitychange', updateDotState); +window.addEventListener('focus', updateDotState); +window.addEventListener('blur', updateDotState); - // 최초 실행 - updateDotState(); +updateDotState(); // ping async function checkStatus() { const cards = document.querySelectorAll(".card"); - const results = []; - for (const card of cards) { + const checkTasks = Array.from(cards).map(async (card) => { const url = card.getAttribute("data-url"); const dot = card.querySelector(".card__decor.card__decor--tr"); - // 결과 저장을 위한 약속(Promise) 생성 - const checkTask = new Promise((resolve) => { - const img = new Image(); - // 5초 타임아웃 설정 - const timeoutId = setTimeout(() => { - img.src = ""; - resolve({ dot, status: "offline" }); - }, 5000); + try { + // mode: 'no-cors'를 사용하면 타 도메인의 리소스 존재 여부만 빠르게 확인 가능 + const response = await fetch(`${url}/favicon.ico?t=${Date.now()}`, { + mode: 'no-cors', + signal: AbortSignal.timeout(5000) + }); - img.onload = () => { - clearTimeout(timeoutId); - resolve({ dot, status: "online" }); - }; + return { dot, status: "online" }; + } catch (error) { + return { dot, status: "offline" }; + } + }); - img.onerror = () => { - clearTimeout(timeoutId); - resolve({ dot, status: "offline" }); // 502 에러 시 이쪽으로 빠집니다. - }; + const finalStatuses = await Promise.all(checkTasks); - // 캐시 방지를 위해 타임스탬프를 붙여서 파비콘 호출 - 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"); @@ -66,9 +49,8 @@ async function checkStatus() { checkStatus(); setInterval(checkStatus, 300000); -// 🔴 테스트 ON +// bolt animation - Test Mode // const TEST_ONLY_SECOND_CARD = true; -// 🟢 테스트 OFF const TEST_ONLY_SECOND_CARD = false; // bolt animation @@ -101,10 +83,9 @@ function startBoltAction() { randomBolt.classList.add("is-acting"); - // setTimeout 대신 이벤트 리스너 사용 randomBolt.addEventListener('animationend', () => { randomBolt.classList.remove("is-acting"); - }, { once: true }); // 메모리 관리를 위해 한 번만 실행 후 제거 + }, { once: true }); } const nextInterval = Math.random() * 4000 + 3000; @@ -116,10 +97,8 @@ window.addEventListener("load", () => { }); window.addEventListener('DOMContentLoaded', () => { - // 1. 상태 체크는 1초 뒤에 여유롭게 시작 setTimeout(checkStatus, 1000); - // 2. 나사 랜덤 각도 설정 (DOM이 준비된 직후 실행) document.querySelectorAll('.card').forEach((card) => { card.querySelectorAll('.card__decor').forEach((decor) => { const angle = Math.floor(Math.random() * 180) - 30; @@ -128,48 +107,39 @@ window.addEventListener('DOMContentLoaded', () => { }); }); -// 1. 사용할 테마 리스트 +// Text Changer const themes = ['semi-nova', 'nova', 'semi-solaris', 'solaris']; const STORAGE_KEY = 'selected-theme'; -// 2. 초기 로드 시 테마 적용 (기본값: semi-nova) const savedTheme = localStorage.getItem(STORAGE_KEY) || themes[0]; document.body.classList.add(savedTheme); window.addEventListener('keydown', (e) => { - // OS별 수정 키 판별 (Mac: Command, Win: Control) 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 && + isModifierPressed && (isLeft || isRight) && - // 입력창 안에서 커서 이동을 방해하지 않도록 체크 !['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName) && - !document.activeElement?.isContentEditable // 에디터 영역 대응 + !document.activeElement?.isContentEditable ) { e.preventDefault(); - // 현재 인덱스 찾기 let currentIndex = themes.findIndex(t => document.body.classList.contains(t)); if (currentIndex === -1) currentIndex = 0; - // 3. 좌/우 방향에 따른 인덱스 순환 if (isRight) { - // 오른쪽 화살표: 다음 테마 currentIndex = (currentIndex + 1) % themes.length; } else if (isLeft) { - // 왼쪽 화살표: 이전 테마 currentIndex = (currentIndex - 1 + themes.length) % themes.length; } const nextTheme = themes[currentIndex]; - // 4. 클래스 교체 및 저장 themes.forEach(t => document.body.classList.remove(t)); document.body.classList.add(nextTheme); localStorage.setItem(STORAGE_KEY, nextTheme); @@ -178,6 +148,7 @@ window.addEventListener('keydown', (e) => { } }); +// Text Changer - Swipe Gesture const logo = document.querySelector('header h1'); let startX = 0; @@ -196,8 +167,6 @@ function applyTheme(index) { const dot = document.querySelector('.dot-point'); if (dot) { dot.classList.remove('blink-alert'); - // 리플로우 유발 코드 제거 (void dot.offsetWidth;) - // 대신 브라우저의 다음 렌더링 사이클을 이용 requestAnimationFrame(() => { dot.classList.add('blink-alert'); }); @@ -237,4 +206,82 @@ logo.addEventListener('pointercancel', reset); function reset() { isDragging = false; logo.classList.remove('is-sliding'); -} \ No newline at end of file +} + +// 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}`); + } +}); \ No newline at end of file diff --git a/style.css b/style.css index bc680c4..22f0869 100644 --- a/style.css +++ b/style.css @@ -1,103 +1,467 @@ -@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; } +@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; +} + +/* ===== Dark Mode (Default) ===== */ +:root { + /* Background Colors */ + --bg-primary: #050505; + --bg-gradient-start: #1a1a1a; + --bg-gradient-end: #050505; + + /* Card Colors */ + --card-bg-gradient-start: #242424; + --card-bg-gradient-end: #111111; + --card-bg-hover-start: #1a1a1a; + --card-bg-hover-end: #0d0d0d; + --card-border: #222222; + --card-border-hover: #333333; + --card-shadow-inset: rgba(255, 255, 255, 0.05); + + /* Text Colors */ + --text-primary: #e0e0e0; + --text-secondary: #eaeaea; + --text-tertiary: #acacac; + --text-muted: #666666; + --text-section-title: #666666; + + /* Header Title Gradient */ + --header-gradient-start: #ffffff; + --header-gradient-end: #a1a1a1; + --header-dot-inactive: linear-gradient(120deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0.12)); + + /* Accent Colors */ + --accent-primary: #00ff88; + --accent-primary-glow: rgba(0, 255, 136, 0.7); + --accent-alert-red: #ff3b3b; + --accent-alert-red-glow: rgba(255, 59, 59, 0.9); + --accent-offline-red: #ff4444; + + /* Border & Divider */ + --border-accent: #00ff88; + --divider-gradient-start: #333333; + + /* Scrollbar */ + --scrollbar-track: #050505; + --scrollbar-thumb-start: #333333; + --scrollbar-thumb-end: #222222; + --scrollbar-thumb-hover: #444444; + --scrollbar-thumb-active: #00ff88; + --scrollbar-thumb-active-glow: rgba(0, 255, 136, 1); + + /* Card Decor */ + --decor-gradient-start: #777777; + --decor-gradient-end: #333333; + --decor-border: #111111; + --decor-bar: #222222; + --decor-bar-active: #000000; + + /* Glass Effect */ + --glass-gradient: linear-gradient(120deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0.12)); + --glass-shadow: rgba(255, 255, 255, 0.25); + --glass-shadow-blur: rgba(0, 0, 0, 0.08); +} + +/* ===== Light Mode ===== */ +:root.light-mode { + /* Background Colors */ + --bg-primary: #fafafa; + --bg-gradient-start: #e5e5e5; + --bg-gradient-end: #fafafa; + + /* Card Colors */ + --card-bg-gradient-start: #ffffff; + --card-bg-gradient-end: #f5f5f5; + --card-bg-hover-start: #ffffff; + --card-bg-hover-end: #f0f0f0; + --card-border: #e0e0e0; + --card-border-hover: #d0d0d0; + --card-shadow-inset: rgba(0, 0, 0, 0.03); + + /* Text Colors */ + --text-primary: #1a1a1a; + --text-secondary: #2a2a2a; + --text-tertiary: #4a4a4a; + --text-muted: #999999; + --text-section-title: #888; + + /* Header Title Gradient */ + --header-gradient-start: #1a1a1a; + --header-gradient-end: #5a5a5a; + --header-dot-inactive: linear-gradient(120deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.08)); + + /* Accent Colors (same) */ + --accent-primary: #00ff88; + --accent-primary-glow: rgba(0, 255, 136, 0.7); + --accent-alert-red: #ff3b3b; + --accent-alert-red-glow: rgba(255, 59, 59, 0.9); + --accent-offline-red: #ff4444; + + /* Border & Divider */ + --border-accent: #00cc6a; + --divider-gradient-start: #d0d0d0; + + /* Scrollbar */ + --scrollbar-track: #f0f0f0; + --scrollbar-thumb-start: #c0c0c0; + --scrollbar-thumb-end: #d0d0d0; + --scrollbar-thumb-hover: #a0a0a0; + --scrollbar-thumb-active: #00ff88; + --scrollbar-thumb-active-glow: rgba(0, 255, 136, 1); + + /* Card Decor */ + --decor-gradient-start: #b0b0b0; + --decor-gradient-end: #d0d0d0; + --decor-border: #e5e5e5; + --decor-bar: #d5d5d5; + --decor-bar-active: #c0c0c0; + + /* Glass Effect */ + --glass-gradient: linear-gradient(120deg, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.05)); + --glass-shadow: rgba(0, 0, 0, 0.15); + --glass-shadow-blur: rgba(0, 0, 0, 0.05); +} + +html { + background-color: var(--bg-primary); +} /* Nova: Stella Sora, Solaris: Wuthering Waves */ -.glyph { font-family: "solaris"; } +.glyph { + font-family: "solaris"; +} -.nova *, .nova *::before, .nova *::after { font-family: "nova"; } -.semi-nova .glyph { font-family: "nova"; } +.nova *, +.nova *::before, +.nova *::after { + font-family: "nova"; +} -.solaris *, .solaris *::before, .solaris *::after { font-family: "solaris"; } -.semi-solaris .glyph { font-family: "solaris"; } +.semi-nova .glyph { + font-family: "nova"; +} + +.solaris *, +.solaris *::before, +.solaris *::after { + font-family: "solaris"; +} + +.semi-solaris .glyph { + font-family: "solaris"; +} /* initial */ -h2 { margin: 0; padding: 0; } +h2 { + margin: 0; + padding: 0; +} /* utility classes */ -.m-0 { margin: 0 } -.mx-auto { margin-left: auto; margin-right: auto; } -.mt-6 { margin-top: 1.5rem; } -.mb-6 { margin-bottom: 1.5rem; } -.p-0 { padding: 0 } -.pt-5 { padding-top: 1.25rem; } -.pt-20 { padding-top: 5rem; } -.pl-1 { padding-left: 1rem; } -.pb-10 { padding-bottom: 2.5rem; } -.pb-12 { padding-bottom: 3rem; } -.px-4 { padding-left: 1rem; padding-right: 1rem; } -.w-full { width: 100%; } -.max-w-full { max-width: 100%; } -.min-w-0 { min-width: 0; } -.h-6 { height: 1.5rem; } -.h-20 { height: 5rem; } -.flex { display: flex } -.flex-column { flex-direction: column } -.justify-center { justify-content: center; } -.align-center { align-items: center } -.keep-all { word-break: keep-all; } -.text-center { text-align: center; } -.text-ellipsis { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } -.overflow-hidden { overflow: hidden; } -.pointer { cursor: pointer; } -.grid { display: grid; } -.grid-col-3 { grid-template-columns: repeat(3, 1fr); } -.gap-6 { gap: 1.5rem; } +.m-0 { + margin: 0 +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.p-0 { + padding: 0 +} + +.pt-5 { + padding-top: 1.25rem; +} + +.pt-20 { + padding-top: 5rem; +} + +.pl-1 { + padding-left: 1rem; +} + +.pb-10 { + padding-bottom: 2.5rem; +} + +.pb-12 { + padding-bottom: 3rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.w-full { + width: 100%; +} + +.max-w-full { + max-width: 100%; +} + +.min-w-0 { + min-width: 0; +} + +.h-6 { + height: 1.5rem; +} + +.h-20 { + height: 5rem; +} + +.flex { + display: flex +} + +.flex-column { + flex-direction: column +} + +.justify-center { + justify-content: center; +} + +.align-center { + align-items: center +} + +.keep-all { + word-break: keep-all; +} + +.text-center { + text-align: center; +} + +.text-ellipsis { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.overflow-hidden { + overflow: hidden; +} + +.pointer { + cursor: pointer; +} + +.grid { + display: grid; +} + +.grid-col-3 { + grid-template-columns: repeat(3, 1fr); +} + +.gap-6 { + gap: 1.5rem; +} /* scrollbar styles */ -* { scrollbar-width: thin; scrollbar-color: #333 #050505; } -::-webkit-scrollbar { width: 8px; height: 8px; } -::-webkit-scrollbar-track { background: #050505; } -::-webkit-scrollbar-thumb { background: linear-gradient(to bottom, #333, #222); border-radius: 10px; border: 2px solid #050505; } -::-webkit-scrollbar-thumb:hover { background: #444; border-color: #050505; } -::-webkit-scrollbar-thumb:active { background: #00ff88; box-shadow: 0 0 10px #00ff88; } +* { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb-start) var(--scrollbar-track); +} + +*::-webkit-scrollbar { + width: var(--scrollbar-track); +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--scrollbar-track); +} + +::-webkit-scrollbar-thumb { + background: linear-gradient(to bottom, var(--scrollbar-thumb-start), var(--scrollbar-thumb-end)); + border-radius: 10px; + border: 2px solid var(--scrollbar-track); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); + border-color: var(--scrollbar-track); +} + +::-webkit-scrollbar-thumb:active { + background: var(--scrollbar-thumb-active); + box-shadow: 0 0 10px var(--scrollbar-thumb-active-glow); +} /* body styles */ body { - background-color: #050505; - background: radial-gradient(circle at 50% 50%, #1a1a1a 0%, #050505 100%); - color: #e0e0e0; - font-family: "Pretendard", system-ui, sans-serif; + background-color: var(--bg-primary); + background: radial-gradient(circle at 50% 50%, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); + color: var(--text-primary); + /* 1순위: 애플 시스템 폰트, 2순위: 노토산스, 3순위: 시스템 기본 고딕 */ + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans KR", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + -webkit-font-smoothing: antialiased; + /* 글자를 더 매끄럽게 보이게 함 */ min-height: 100vh; } /* header styles */ -.header-title { font-size:3.5rem; font-weight:900; letter-spacing:4px; text-transform:lowercase; cursor:pointer; user-select:none; -webkit-user-select:none; -webkit-touch-callout:none; touch-action:pan-y; background:linear-gradient(to bottom, #ffffff 30%, #a1a1a1 100%); -webkit-background-clip:text; -webkit-text-fill-color:transparent; filter:drop-shadow(0 2px 4px rgba(0,0,0,0.5)); } -.header-title.is-sliding { opacity: 0.85; } -.header-title .dot-point { display:inline-block; margin:0 2px; background:none; -webkit-background-clip:initial; -webkit-text-fill-color:initial; color:linear-gradient(120deg, rgba(255,255,255,0.12), rgba(255,255,255,0.25), rgba(255,255,255,0.12)); } -.header-title .dot-point.is-active { color:#00ff88; animation:pulse-dot 2s infinite; } -.header-title.is-pressing .dot-point { animation-duration: 0.4s; } -.header-title .dot-point.blink-alert { animation: blink-red 0.9s ease-in-out; } -.solaris .header-title { font-size: 2.25rem; margin-top: 1.5rem; } +.header-title { + font-size: 3.5rem; + font-weight: 900; + letter-spacing: 4px; + text-transform: lowercase; + cursor: pointer; + user-select: none; + -webkit-user-select: none; + -webkit-touch-callout: none; + touch-action: pan-y; + background: linear-gradient(to bottom, #ffffff 30%, #a1a1a1 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5)); +} + +.header-title.is-sliding { + opacity: 0.85; +} + +.header-title .dot-point { + display: inline-block; + margin: 0 2px; + background: none; + -webkit-background-clip: initial; + -webkit-text-fill-color: initial; + color: linear-gradient(120deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0.12)); +} + +.header-title .dot-point.is-active { + color: var(--accent-primary); + animation: pulse-dot 2s infinite; +} + +.header-title.is-pressing .dot-point { + animation-duration: 0.4s; +} + +.header-title .dot-point.blink-alert { + animation: blink-red 0.9s ease-in-out; +} + +.solaris .header-title { + font-size: 2.25rem; + margin-top: 1.5rem; +} /* section styles */ -.category-section { width: 95%; max-width: 1440px; } -.section-title { font-size:1.25rem; letter-spacing:3px; text-transform:uppercase; color:#666; border-left:3px solid #00ff88; } +.category-section { + width: 95%; + max-width: 1440px; +} + +.section-title { + font-size: 1.25rem; + letter-spacing: 3px; + text-transform: uppercase; + color: #666; + border-left: 3px solid var(--accent-primary); +} .section-title::after { content: ""; flex: 1; height: 1px; - background: linear-gradient(to right, #333, transparent); + background: linear-gradient(to right, var(--divider-gradient-start), transparent); margin-left: 1.25rem; } @media (max-width: 1200px) { - .category-section { max-width: 900px; } - .container { grid-template-columns: repeat(2, 1fr); } + .category-section { + max-width: 900px; + } + + .container { + grid-template-columns: repeat(2, 1fr); + } } @media (max-width: 768px) { - .category-section { max-width: 500px; margin-bottom: 40px; } - .container { grid-template-columns: 1fr; gap: 1.25rem; } - .section-title { font-size: 1rem; letter-spacing: 2px; } + .category-section { + max-width: 500px; + margin-bottom: 40px; + } + + .container { + grid-template-columns: 1fr; + gap: 1.25rem; + } + + .section-title { + font-size: 1rem; + letter-spacing: 2px; + } } /* card styles */ -.card { position: relative; display: flex; flex-direction: column; gap: 1.5rem; background: linear-gradient(145deg, #242424 0%, #111111 100%); backdrop-filter: blur(40px); -webkit-backdrop-filter: blur(40px); border: 1px solid #222222; box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.05); border-radius: 24px; padding: 3rem 1.5rem; text-decoration: none; color: inherit; transition: all 0.4s ease; } -.card:hover { transform: translateY(-8px); background: linear-gradient(145deg, #1a1a1a 0%, #0d0d0d 100%); border-color: #333333; } +.card { + position: relative; + display: flex; + flex-direction: column; + gap: 1.5rem; + background: linear-gradient(145deg, var(--card-bg-gradient-start) 0%, var(--card-bg-gradient-end) 100%); + backdrop-filter: blur(40px); + -webkit-backdrop-filter: blur(40px); + border: 1px solid var(--card-border); + box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.05); + border-radius: 24px; + padding: 3rem 1.5rem; + text-decoration: none; + color: inherit; + transition: all 0.4s ease; +} + +.card:hover { + transform: translateY(-8px); + background: linear-gradient(145deg, var(--card-bg-hover-start) 0%, var(--card-bg-hover-end) 100%); + border-color: var(--card-border-hover); +} /* card title */ -.card__title { height: 2rem; display: flex; justify-content: center; align-items: center; font-size: 1.5rem; line-height: 1; -webkit-text-stroke: 1px rgba(255, 255, 255, 0.5); } +.card__title { + height: 2rem; + display: flex; + justify-content: center; + align-items: center; + font-size: 1.5rem; + line-height: 1; + -webkit-text-stroke: 1px rgba(255, 255, 255, 0.5); +} .nova .card__title, .semi-nova .card__title { @@ -105,24 +469,49 @@ body { } /* card subtitle */ -.card__subtitle { height: 1rem; max-width: 100%; min-width: 0; font-size: 12px; font-weight: 700; color: #eaeaea; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; } +.card__subtitle { + height: 1rem; + max-width: 100%; + min-width: 0; + font-size: 12px; + font-weight: 700; + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; +} .solaris .card__subtitle, .nova .card__subtitle { - position:relative; - color:transparent + position: relative; + color: transparent } -.solaris .card__subtitle::after, .nova .card__subtitle::after { - content:""; position:absolute; display:block; top:0; left:0; width:100%; height:1rem; opacity:0.25; background:linear-gradient(120deg, rgba(255,255,255,0.12), rgba(255,255,255,0.25), rgba(255,255,255,0.12)); background-size:200% 100%; backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px); box-shadow:inset 0 1px 0 rgba(255,255,255,0.25), 0 4px 12px rgba(0,0,0,0.08); animation:glass-skeleton 1.6s ease-in-out infinite; +.solaris .card__subtitle::after, +.nova .card__subtitle::after { + content: ""; + position: absolute; + display: block; + top: 0; + left: 0; + width: 100%; + height: 1rem; + opacity: 0.25; + background: linear-gradient(120deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0.12)); + background-size: 200% 100%; + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 4px 12px rgba(0, 0, 0, 0.08); + animation: glass-skeleton 1.6s ease-in-out infinite; } .solaris .card:hover .card__subtitle, .nova .card:hover .card__subtitle { display: block; - color: #eaeaea; - font-family: "Pretendard", sans-serif; + color: var(--text-secondarya); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans KR", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } .solaris .card:hover .card__subtitle:after, @@ -132,55 +521,185 @@ body { /* card body */ -.card__body { display:flex; flex-direction:column; justify-content:center; flex-grow:1; min-width:0; } +.card__body { + display: flex; + flex-direction: column; + justify-content: center; + flex-grow: 1; + min-width: 0; +} -.card__meta--url { display:flex; flex:1; justify-content:center; align-items:center; height:1.5rem; min-height:1.5rem; max-height:1.5rem; font-size:1.125rem; font-weight:700; letter-spacing:-0.3px; text-transform:lowercase; text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:#acacac; } -.solaris .card__meta--url { font-size: 1rem; } -.nova .card__meta--url { font-size: 1.5rem; } +.card__meta--url { + display: flex; + flex: 1; + justify-content: center; + align-items: center; + height: 1.5rem; + min-height: 1.5rem; + max-height: 1.5rem; + font-size: 1.125rem; + font-weight: 700; + letter-spacing: -0.3px; + text-transform: lowercase; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: var(--text-tertiary); +} -.card__desc { display:flex; justify-content:center; align-items:center; margin:0; min-height:1.125rem; font-size:0.85rem; line-height:1.125rem; font-weight:400; text-align:center; color:#666666; } -.card__desc[data-lang="en"] { display: none; } +.solaris .card__meta--url { + font-size: 1rem; +} +.nova .card__meta--url { + font-size: 1.5rem; +} +.card__desc { + display: flex; + justify-content: center; + align-items: center; + margin: 0; + min-height: 1.125rem; + font-size: 0.85rem; + line-height: 1.125rem; + font-weight: 400; + text-align: center; + color: var(--text-muted); +} -.status-online { background-color:#00ff88; box-shadow:0 0 10px #00ff88; animation:pulse 2s infinite; } -.status-offline { background-color:#ff4444; box-shadow:0 0 10px #ff4444; animation:none; } +.card__desc[data-lang="en"] { + display: none; +} +.status-online { + background-color: var(--accent-primary); + box-shadow: 0 0 10px var(--accent-primary-glow); + animation: pulse 2s infinite; +} +.status-offline { + background-color: var(--accent-offline-red); + box-shadow: 0 0 10px var(--accent-offline-red); + animation: none; +} +.solaris .section-title { + font-size: 1.25rem; +} +.nova .section-title { + font-size: 1.75rem; +} - - -.solaris .section-title { font-size: 1.25rem; } -.nova .section-title { font-size: 1.75rem; } .solaris .card__desc[data-lang="ko"], -.nova .card__desc[data-lang="ko"] { display:none; } +.nova .card__desc[data-lang="ko"] { + display: none; +} + .solaris .card__desc[data-lang="ko"], -.nova .card__desc[data-lang="ko"] { display:none; } -.solaris .card__desc[data-lang="en"] { display:block; margin:0; font-size:0.875rem; } -.nova .card__desc[data-lang="en"] { display:block; margin:0; font-size:1.25rem; } +.nova .card__desc[data-lang="ko"] { + display: none; +} + +.solaris .card__desc[data-lang="en"] { + display: block; + margin: 0; + font-size: 0.875rem; +} + +.nova .card__desc[data-lang="en"] { + display: block; + margin: 0; + font-size: 1.25rem; +} /* card decor */ -.card__decor-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; } -.card__decor { position: absolute; width: 1rem; height: 1rem; background: linear-gradient(135deg, #777 0%, #333 100%); border-radius: 50%; border: 1px solid #111; } +.card__decor-layer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; +} -.card__decor--tl { top: 1rem; left: 1rem; } -.card__decor--tr { top: 1rem; right: 1rem; } -.card__decor--bl { bottom: 1rem; left: 1rem; } -.card__decor--br { bottom: 1rem; right: 1rem; } +.card__decor { + display: inline-block; + position: absolute; + width: 1rem; + height: 1rem; + background: linear-gradient(135deg, var(--decor-gradient-start) 0%, var(--decor-gradient-end) 100%); + border-radius: 50%; + border: 1px solid var(--decor-border); +} -.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.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; } +.card__decor--tl { + top: 1rem; + left: 1rem; +} + +.card__decor--tr { + top: 1rem; + right: 1rem; +} + +.card__decor--bl { + bottom: 1rem; + left: 1rem; +} + +.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: var(--decor-bar); + transform: translate(-50%, -50%) rotate(var(--r, 0deg)); + transition: all 0.3s ease; +} + +.card__decor.is-acting::before { + transform: translate(-50%, -50%) rotate(calc(var(--r, 0deg) + 45deg)); + background: var(--decor-bar-active); +} + +.is-acting::before { + animation: bolt-spin-return 16s cubic-bezier(0.65, 0, 0.35, 1) forwards; +} /* keyframes */ @keyframes blink-red { - 0%, 100% { color: currentColor; opacity: 1; text-shadow: none; } - 20%, 60% { color: #ff3b3b; text-shadow: 0 0 6px rgba(255, 59, 59, 0.9); } - 40%, 80% { color: currentColor; text-shadow: none; } + + 0%, + 100% { + color: currentColor; + opacity: 1; + text-shadow: none; + } + + 20%, + 60% { + color: var(--accent-alert-redred); + text-shadow: 0 0 6px var(--accent-alert-red-glow); + } + + 40%, + 80% { + color: currentColor; + text-shadow: none; + } } header h1 .dot-point.pulse-once { @@ -188,31 +707,222 @@ header h1 .dot-point.pulse-once { } @keyframes pulse-once { - 0% { transform: scale(1); opacity: 0.7; text-shadow: 0 0 6px rgba(0, 255, 136, 0.6); } - 40% { transform: scale(5); opacity: 1; text-shadow: 0 0 14px rgba(0, 255, 136, 1); } - 100% { transform: scale(1); opacity: 0.9; text-shadow: 0 0 8px rgba(0, 255, 136, 0.8); } + 0% { + transform: scale(1); + opacity: 0.7; + text-shadow: 0 0 6px rgba(0, 255, 136, 0.6); + } + + 40% { + transform: scale(5); + opacity: 1; + text-shadow: 0 0 14px rgba(0, 255, 136, 1); + } + + 100% { + transform: scale(1); + opacity: 0.9; + text-shadow: 0 0 8px rgba(0, 255, 136, 0.8); + } } @keyframes pulse { - 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(0, 255, 136, 0.7); } - 70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(0, 255, 136, 0); } - 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(0, 255, 136, 0); } + 0% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba(0, 255, 136, 0.7); + } + + 70% { + transform: scale(1); + box-shadow: 0 0 0 10px rgba(0, 255, 136, 0); + } + + 100% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba(0, 255, 136, 0); + } } @keyframes pulse-dot { - 0%, 100% { opacity: 0.5; text-shadow: 0 0 8px rgba(0, 255, 136, 0.8); } - 50% { opacity: 1; text-shadow: 0 0 2px rgba(0, 255, 136, 0.2); } + + 0%, + 100% { + opacity: 0.5; + text-shadow: 0 0 8px rgba(0, 255, 136, 0.8); + } + + 50% { + opacity: 1; + text-shadow: 0 0 2px rgba(0, 255, 136, 0.2); + } } @keyframes bolt-spin-return { - 0% { transform: translate(-50%, -50%) rotate(var(--r)); } - 60% { transform: translate(-50%, -50%) rotate(calc(var(--r) + 1080deg)); } - 70% { transform: translate(-50%, -50%) rotate(calc(var(--r) + 1080deg)); } - 100% { transform: translate(-50%, -50%) rotate(var(--r)); } + 0% { + transform: translate(-50%, -50%) rotate(var(--r)); + } + + 60% { + transform: translate(-50%, -50%) rotate(calc(var(--r) + 1080deg)); + } + + 70% { + transform: translate(-50%, -50%) rotate(calc(var(--r) + 1080deg)); + } + + 100% { + transform: translate(-50%, -50%) rotate(var(--r)); + } } @keyframes glass-skeleton { - 0% { background-position: 0% 0; } - 50% { background-position: 100% 0; } - 100% { background-position: 0% 0; } + 0% { + background-position: 0% 0; + } + + 50% { + background-position: 100% 0; + } + + 100% { + background-position: 0% 0; + } +} + +/* Theme Toggle */ +.theme-toggle-wrapper { + position: fixed; + top: 2rem; + right: 2rem; + z-index: 1000; +} + +.theme-toggle-btn { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + background: linear-gradient(145deg, var(--card-bg-gradient-start), var(--card-bg-gradient-end)); + border: 1px solid var(--card-border); + border-radius: 50%; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.theme-toggle-btn:hover { + transform: scale(1.05); + border-color: var(--card-border-hover); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2); +} + +.theme-icon { + font-size: 1.5rem; + color: var(--text-primary); + transition: transform 0.3s ease; +} + +.theme-toggle-wrapper.is-open .theme-icon { + transform: rotate(180deg); +} + +.theme-options { + position: absolute; + top: 100%; + right: 0; + margin-top: 0.75rem; + background: linear-gradient(145deg, var(--card-bg-gradient-start), var(--card-bg-gradient-end)); + border: 1px solid var(--card-border); + border-radius: 16px; + overflow: hidden; + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); + min-width: 140px; +} + +.theme-toggle-wrapper.is-open .theme-options { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.theme-option { + display: flex; + align-items: center; + gap: 0.75rem; + width: 100%; + padding: 0.875rem 1.25rem; + background: transparent; + border: none; + cursor: pointer; + transition: all 0.2s ease; + color: var(--text-primary); + border-bottom: 1px solid var(--card-border); +} + +.theme-option:last-child { + border-bottom: none; +} + +.theme-option:hover { + background: rgba(0, 255, 136, 0.08); +} + +.theme-option.is-active { + background: rgba(0, 255, 136, 0.12); + color: var(--accent-primary); +} + +.theme-option-icon { + font-size: 1.25rem; + transition: transform 0.2s ease; +} + +.theme-option:hover .theme-option-icon { + transform: scale(1.15); +} + +.theme-option.is-active .theme-option-icon { + animation: pulse-theme 2s infinite; +} + +.theme-option-label { + font-size: 0.875rem; + font-weight: 600; + letter-spacing: 0.5px; +} + +@keyframes pulse-theme { + + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* 모바일 대응 */ +@media (max-width: 768px) { + .theme-toggle-wrapper { + top: 1.25rem; + right: 1.25rem; + } + + .theme-toggle-btn { + width: 1.75rem; + height: 1.75rem; + } + + .theme-icon { + font-size: 1.25rem; + } } \ No newline at end of file