diff --git a/src/views/Main.vue b/src/views/Main.vue index 1a8775c..5208e89 100644 --- a/src/views/Main.vue +++ b/src/views/Main.vue @@ -5,6 +5,10 @@ SPECIALSOURCE.SYSTEM + + [{{ currentServerSourceDisplay }}] + +
@@ -171,6 +175,9 @@ const todayUnique = ref(0); const activeNow = ref(0); let analyticsTimer = null; +// ๐ข [NEW] NAS ์์กด ์ฌ๋ถ ์ค์๊ฐ ํ๋จ์ฉ ๋ฐ์ํ ๋ณ์ +const isNasOnline = ref(true); + const menuItems = [ { id: 'main', name: 'HOME' }, { id: 'youtube', name: 'YOUTUBE' }, @@ -181,6 +188,36 @@ const menuItems = [ const isLoggedIn = computed(() => !!authToken.value); +// ๐ข [NEW] ํ์ฌ ํ์ฑํ๋ ์๋ฒ ํ๋ณ ์์ง (์๊ฐ + API ์์กด ์ฌ๋ถ ์กฐํฉ) +const currentServerSource = computed(() => { + const currentHour = new Date().getUTCHours() + 9; // ํ๊ตญ ํ์ค์(KST) ๋ณด์ + const localHour = currentHour % 24; + + // 1. ๋ฐค 12์ ~ ์์นจ 8์ ์ฌ์ด (์์ปค๊ฐ ํด๊ทผ์ํค๊ณ ํ์ด์ด๋ฒ ์ด์ค๋ก ๋ฌด์กฐ๊ฑด ์ฐํํ๋ ์ ๊ธฐ ๋ฐฑ์ ์๊ฐ๋) + if (localHour >= 0 && localHour < 8) { + return 'firebase_backup'; + } + + // 2. ๋ฎ ์๊ฐ๋์ธ๋ฐ NAS ํต์ ์ด ๋๊ฒผ๊ฑฐ๋ ์ค๋ฅ๊ฐ ๋์ ํ์ด์ด๋ฒ ์ด์ค ์ฐํ ๋ํผ์๋ก ๊ธด๊ธ ์ด๋๋ ์ํ + if (!isNasOnline.value) { + return 'firebase_failover'; + } + + // 3. ๋ฎ ์๊ฐ๋์ NAS ์๋ฒ๊ฐ ๊ฑด๊ฐํ๊ฒ ์๋น์ค๋ฅผ ์ ๋ฌํ๊ณ ์๋ ์ต๊ณ ๊ถํ ๋ฉ์ธ ๋ ธ๋ ์ํ + return 'nas_main'; +}); + +// ๐ข [NEW] ํฐ๋ฏธ๋ ์ฝ์ ๊ฐ์ฑ์ ๋ฌธ์์ด ๋งตํ ์ถ๋ ฅ ๊ธฐ๋ฏน +const currentServerSourceDisplay = computed(() => { + if (currentServerSource.value === 'firebase_backup') { + return 'NODE:FB_NIGHT_MODE'; + } + if (currentServerSource.value === 'firebase_failover') { + return 'NODE:FB_FAILOVER'; + } + return 'NODE:NAS_ACTIVE'; +}); + const rawAscii = ` โโโโโโโโโโโโโโโ โโโโโโโโ โโโโโโโโโโ โโโโโโ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ @@ -205,6 +242,7 @@ const formattedAscii = computed(() => { return rawAscii.replace(/ /g, 'โ'); }); +// ๐ข [MODIFY] 30์ด ๊ฐ๊ฒฉ์ผ๋ก ์คํ์ ๊ธ์ด์ฌ ๋ NAS๊ฐ ๊ฑด๊ฐํ๊ฒ ์๋ตํ๋์ง ์ฒดํฌํ๋ ์ ํจ์ฑ ๋ก์ง ์ถ๊ฐ const fetchAnalyticsData = async () => { try { const apiBaseUrl = import.meta.env.VITE_API_URL || ''; @@ -212,9 +250,11 @@ const fetchAnalyticsData = async () => { if (res.data) { todayUnique.value = res.data.todayUnique || 0; activeNow.value = res.data.activeNow || 0; + isNasOnline.value = true; // ๐ข ์ ์ ๋์ ํ์ธ -> 'NODE:NAS_ACTIVE' ์ถ๋ ฅ ๋ณด์ฅ } } catch (err) { console.error("๊ด์ ํต๊ณ ๋ฐ์ดํฐ ๋ก๋ ์คํจ:", err); + isNasOnline.value = false; // ๐ข ์๋ฌ ๋ฐ์ ์ ์๋์ผ๋ก ๋น์๋ง ๋ฐฐ์ง('NODE:FB_FAILOVER')๋ก ๊ต์ฒด } }; @@ -374,12 +414,10 @@ onMounted(() => { 'specialsource': function () { this.echo('SPECIALSOURCE Established 2021.01.'); }, 'email': function () { this.echo('specialsource.company@gmail.com'); }, - // ๐ข [NEW] ๋ฆฌ์ผํ์ ๋ ์ด์์ ํ ์คํธ ์์ฐจ ํ์ดํ ํ๋ฆฐํ ๊ธฐ๋ฏน ํจ์ ๊ตฌํ 'status': function () { const term = this; - term.pause(); // ํ์ดํ ์งํ ์ค์ ์ถ๊ฐ ๋ช ๋ น์ด ์ ๋ ฅ์ ์ ๊ธ(์ธํ๋ผ ๋ณดํธ ๊ฐ์ฑ) + term.pause(); - // ํ๋ฆฐํ ํ ๋คํ ํ๋ ฌ ๋ฐฐ์ดํ const lines = [ "============================================", " ๐ SPECIALSOURCE TRAFFIC ARCHITECTURE", @@ -393,14 +431,13 @@ onMounted(() => { term.echo("[SYS] EXTRACTING POSTGRESQL METADATA... SNAPSHOT READY."); let index = 0; - // 110ms ๊ฐ๊ฒฉ์ผ๋ก ์คํ๊ฑฐ๋ ๋ผ์ธ ์ถ๋ ฅ ๊ธฐ๋ฏน ๊ธฐ๋ const typingInterval = setInterval(() => { if (index < lines.length) { term.echo(lines[index]); index++; } else { clearInterval(typingInterval); - term.resume(); // ํ์ดํ ์ข ๋ฃ ํ ์ ์ ์ ๋ ฅ ์ธ๋ฝ + term.resume(); } }, 110); }, @@ -434,6 +471,7 @@ onUnmounted(() => { .contact-form, .theme-selector, .nav-stats, +.server-badge, :deep(.terminal), :deep(.terminal span) { font-family: 'JetBrains Mono', monospace !important; @@ -513,6 +551,37 @@ onUnmounted(() => { box-shadow: 0 0 8px var(--tui-glow); } +/* ๐ข [NEW] ํฐ๋ฏธ๋ ๊ฐ์ฑ์ ์ค์๊ฐ ๊ฐ์ง ์๋ฒ ๋ฐฐ์ง ์คํ์ผ๋ง */ +.server-badge { + font-size: 11px; + font-weight: bold; + padding: 3px 8px; + border: 1px solid currentColor; + background: rgba(0, 0, 0, 0.5); + letter-spacing: 1px; + border-radius: 2px; + transition: 0.3s; +} +/* NAS ํ์ฑํ ์ํ (๊ทธ๋ฆฐ ๊ณ์ด/ํ์ฌ ํ ๋ง ์ปฌ๋ฌ ์ ์ฉ) */ +.server-badge.nas_main { + color: var(--tui-color); + text-shadow: 0 0 5px var(--tui-glow); + border-color: var(--tui-border); +} +/* ์ ๊ธฐ ์ผ๊ฐ Firebase ๋ฐฑ์ ์๋ ์ํ (ํธ๋ฐ์) */ +.server-badge.firebase_backup { + color: #ffb000; + text-shadow: 0 0 5px rgba(255, 176, 0, 0.6); + border-color: rgba(255, 176, 0, 0.3); +} +/* ๋๋ฎ ๋น์ ํต์ ์ฅ์ Firebase ๋ฐฑ์ ๊ฐ๋ ์ํ (๊ฐ๋ ฅํ ๋ ๋ ๊ฒฝ๊ณ & ์ ๋ฉธ) */ +.server-badge.firebase_failover { + color: #ff3b3b; + text-shadow: 0 0 8px #ff3b3b; + border-color: rgba(255, 59, 59, 0.4); + animation: badgeBlink 1.5s infinite ease-in-out; +} + /* --- GNB --- */ .auth-nav { display: flex; @@ -749,6 +818,12 @@ iframe { width: 100%; height: 180px; border-radius: 4px; } 50% { opacity: 1; text-shadow: 0 0 8px var(--tui-glow); } } +/* ๐ข [NEW] ๋น์ Failover ์ ๋ฉธ ์ ๋๋ฉ์ด์ */ +@keyframes badgeBlink { + 0%, 100% { opacity: 0.6; box-shadow: 0 0 5px rgba(255, 59, 59, 0.2); } + 50% { opacity: 1; box-shadow: 0 0 15px rgba(255, 59, 59, 0.6); text-shadow: 0 0 12px #ff3b3b; } +} + /* ์ผํ์ฑ ์ฌ์ด๋ฒ ๊ธ๋ฆฌ์น ํคํ๋ ์ */ @keyframes once-cyber-glitch-wobble { 0%, 25% { transform: translate(0) scale(1) skewX(0deg); color: var(--tui-color) !important; text-shadow: 0 0 8px var(--tui-glow) !important; opacity: 1; }