diff --git a/apps/web/public/app.js b/apps/web/public/app.js
index cb0a221..37a9295 100644
--- a/apps/web/public/app.js
+++ b/apps/web/public/app.js
@@ -18,6 +18,7 @@ const state = {
vehicleSearch: "",
vehicleStatusFilter: "all",
eventSource: null,
+ launchTimer: 0,
activeView: "locations",
adminToken: new URLSearchParams(window.location.search).get("adminToken") ||
window.localStorage.getItem("cargoRadarAdminToken") ||
@@ -26,12 +27,35 @@ const state = {
const LOGIN_SESSION_KEY = "cargoRadarLoginReady";
const LOGIN_NAME_KEY = "cargoRadarLoginName";
+const LAUNCH_STEPS = [
+ {
+ title: "관제 데이터 연결 중",
+ message: "운행 차량의 최신 위치를 불러오고 있습니다."
+ },
+ {
+ title: "차량 위치 불러오는 중",
+ message: "수신 중인 차량과 화물 정보를 정리합니다."
+ },
+ {
+ title: "경로 데이터 확인 중",
+ message: "목적지 경로와 도착 시간을 확인합니다."
+ },
+ {
+ title: "관제 화면 준비 완료",
+ message: "실시간 관제 화면으로 이동합니다."
+ }
+];
const elements = {
loginScreen: document.getElementById("loginScreen"),
loginForm: document.getElementById("loginForm"),
loginNameInput: document.getElementById("loginNameInput"),
loginCodeInput: document.getElementById("loginCodeInput"),
+ launchScreen: document.getElementById("launchScreen"),
+ launchTitle: document.getElementById("launchTitle"),
+ launchMessage: document.getElementById("launchMessage"),
+ launchProgress: document.getElementById("launchProgress"),
+ launchSteps: [...document.querySelectorAll("[data-launch-step]")],
connectionStatus: document.getElementById("connectionStatus"),
systemClock: document.getElementById("systemClock"),
refreshButton: document.getElementById("refreshButton"),
@@ -320,11 +344,12 @@ function submitLoginForm(event) {
window.localStorage.setItem(LOGIN_NAME_KEY, name || "관제 담당자");
window.localStorage.setItem("cargoRadarAdminToken", state.adminToken);
elements.loginCodeInput.value = "";
- hideLoginScreen();
+ beginLaunchSequence();
startDataServices();
}
function showLoginScreen(options = {}) {
+ clearLaunchSequence();
window.localStorage.removeItem(LOGIN_SESSION_KEY);
if (state.eventSource) {
state.eventSource.close();
@@ -352,6 +377,56 @@ function hideLoginScreen(options = {}) {
}, 620);
}
+function beginLaunchSequence() {
+ clearLaunchSequence();
+ document.body.classList.add("is-launch-active");
+ setLaunchStep(0);
+ hideLoginScreen();
+
+ let stepIndex = 0;
+ const advance = () => {
+ stepIndex += 1;
+ if (stepIndex >= LAUNCH_STEPS.length) {
+ finishLaunchSequence();
+ return;
+ }
+
+ setLaunchStep(stepIndex);
+ state.launchTimer = window.setTimeout(advance, stepIndex === LAUNCH_STEPS.length - 1 ? 460 : 620);
+ };
+
+ state.launchTimer = window.setTimeout(advance, 620);
+}
+
+function setLaunchStep(index) {
+ const step = LAUNCH_STEPS[index] || LAUNCH_STEPS[0];
+ elements.launchTitle.textContent = step.title;
+ elements.launchMessage.textContent = step.message;
+ elements.launchProgress.style.width = `${Math.min(100, ((index + 1) / LAUNCH_STEPS.length) * 100)}%`;
+
+ for (const item of elements.launchSteps) {
+ const itemIndex = Number(item.dataset.launchStep);
+ item.classList.toggle("is-active", itemIndex === index);
+ item.classList.toggle("is-complete", itemIndex < index);
+ }
+}
+
+function finishLaunchSequence() {
+ document.body.classList.add("is-launch-closing");
+ state.launchTimer = window.setTimeout(() => {
+ document.body.classList.remove("is-launch-active", "is-launch-closing");
+ state.launchTimer = 0;
+ }, 360);
+}
+
+function clearLaunchSequence() {
+ if (state.launchTimer) {
+ window.clearTimeout(state.launchTimer);
+ state.launchTimer = 0;
+ }
+ document.body.classList.remove("is-launch-active", "is-launch-closing");
+}
+
function startSystemClock() {
const updateClock = () => {
const now = new Date();
diff --git a/apps/web/public/index.html b/apps/web/public/index.html
index 4f11aed..2168c05 100644
--- a/apps/web/public/index.html
+++ b/apps/web/public/index.html
@@ -43,6 +43,22 @@
+ 운행 차량의 최신 위치를 불러오고 있습니다.
+
+