-
+
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