Compare commits

...
2 Commits
+80 -5
View File
@@ -5,6 +5,10 @@
<span class="status-dot" :class="{ 'secure-mode': isLoggedIn }"></span>
<span class="brand">SPECIALSOURCE.SYSTEM</span>
<span :class="['server-badge', currentServerSource]">
[{{ currentServerSourceDisplay }}]
</span>
<div v-if="isLoggedIn" class="nav-stats">
[LIVE:<span class="pulse-text">{{ activeNow }}</span>|TODAY:{{ todayUnique }}]
</div>
@@ -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, '<span class="bg-block">█</span>');
});
// 🟢 [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; }