1028 lines
50 KiB
HTML
1028 lines
50 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>INVION - Preview</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
|
<style>
|
|
/* ===== Design Tokens ===== */
|
|
:root {
|
|
--bg: #fafaff;
|
|
--bg-subtle: #f3f2fa;
|
|
--surface: #ffffff;
|
|
--surface-hover: #f7f6fd;
|
|
--text: #0f0e1a;
|
|
--text-secondary: #6b6a80;
|
|
--text-muted: #9998ad;
|
|
--primary: #6c5ce7;
|
|
--primary-light: #a29bfe;
|
|
--primary-dark: #5041c2;
|
|
--primary-glow: rgba(108, 92, 231, 0.25);
|
|
--accent-cyan: #00cec9;
|
|
--accent-cyan-light: #55efc4;
|
|
--accent-pink: #fd79a8;
|
|
--destructive: #ff4757;
|
|
--success: #00b894;
|
|
--warning: #fdcb6e;
|
|
--border: rgba(108, 92, 231, 0.1);
|
|
--border-subtle: rgba(0,0,0,0.06);
|
|
--card-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(108,92,231,0.06);
|
|
--glow-sm: 0 0 15px rgba(108, 92, 231, 0.15);
|
|
--glow-md: 0 0 30px rgba(108, 92, 231, 0.2);
|
|
--glow-lg: 0 0 60px rgba(108, 92, 231, 0.25);
|
|
--sidebar-bg: #f5f4fc;
|
|
--sidebar-width: 232px;
|
|
--header-height: 48px;
|
|
--tabbar-height: 36px;
|
|
}
|
|
|
|
.dark {
|
|
--bg: #08070f;
|
|
--bg-subtle: #0e0d1a;
|
|
--surface: #13122a;
|
|
--surface-hover: #1a1940;
|
|
--text: #e8e6f0;
|
|
--text-secondary: #8b89a6;
|
|
--text-muted: #5c5a75;
|
|
--primary: #a29bfe;
|
|
--primary-light: #c8c4ff;
|
|
--primary-dark: #6c5ce7;
|
|
--primary-glow: rgba(162, 155, 254, 0.3);
|
|
--accent-cyan: #55efc4;
|
|
--accent-cyan-light: #7dfcd2;
|
|
--accent-pink: #fd79a8;
|
|
--destructive: #ff6b6b;
|
|
--success: #55efc4;
|
|
--warning: #ffeaa7;
|
|
--border: rgba(162, 155, 254, 0.12);
|
|
--border-subtle: rgba(255,255,255,0.05);
|
|
--card-shadow: 0 1px 3px rgba(0,0,0,0.3), 0 8px 24px rgba(0,0,0,0.4);
|
|
--glow-sm: 0 0 15px rgba(162, 155, 254, 0.15);
|
|
--glow-md: 0 0 30px rgba(162, 155, 254, 0.2);
|
|
--glow-lg: 0 0 60px rgba(162, 155, 254, 0.3);
|
|
--sidebar-bg: #0b0a16;
|
|
}
|
|
|
|
/* ===== Reset ===== */
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
html, body { height: 100%; overflow: hidden; }
|
|
body {
|
|
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
transition: background 0.6s cubic-bezier(0.4, 0, 0.2, 1), color 0.4s ease;
|
|
}
|
|
|
|
::-webkit-scrollbar { width: 5px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb { background: rgba(108,92,231,0.15); border-radius: 3px; }
|
|
::-webkit-scrollbar-thumb:hover { background: rgba(108,92,231,0.3); }
|
|
|
|
.screen { position: fixed; inset: 0; transition: opacity 0.6s, transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
.screen.hidden { opacity: 0; pointer-events: none; transform: scale(1.02); }
|
|
|
|
/* ============================================
|
|
LOGIN SCREEN
|
|
============================================ */
|
|
#login-screen {
|
|
display: flex; align-items: center; justify-content: center;
|
|
background: var(--bg); overflow: hidden;
|
|
}
|
|
|
|
.starfield { position: absolute; inset: 0; overflow: hidden; }
|
|
.star {
|
|
position: absolute; width: 2px; height: 2px; background: white; border-radius: 50%;
|
|
animation: twinkle var(--duration, 3s) ease-in-out infinite alternate;
|
|
animation-delay: var(--delay, 0s); opacity: 0;
|
|
}
|
|
.star.colored { background: var(--star-color); width: 3px; height: 3px; }
|
|
@keyframes twinkle {
|
|
0% { opacity: 0; transform: scale(0.5); }
|
|
100% { opacity: var(--max-opacity, 0.7); transform: scale(1); }
|
|
}
|
|
|
|
.nebula {
|
|
position: absolute; border-radius: 50%; filter: blur(120px); pointer-events: none;
|
|
animation: nebula-drift 12s ease-in-out infinite alternate;
|
|
}
|
|
.nebula-1 { width: 600px; height: 600px; top: -15%; right: -10%; background: radial-gradient(circle, var(--primary-glow) 0%, transparent 70%); animation-duration: 15s; }
|
|
.nebula-2 { width: 500px; height: 500px; bottom: -20%; left: -10%; background: radial-gradient(circle, rgba(0,206,201,0.15) 0%, transparent 70%); animation-duration: 12s; animation-delay: -3s; }
|
|
.nebula-3 { width: 350px; height: 350px; top: 40%; left: 45%; background: radial-gradient(circle, rgba(253,121,168,0.12) 0%, transparent 70%); animation-duration: 10s; animation-delay: -6s; }
|
|
@keyframes nebula-drift {
|
|
0% { transform: translate(0, 0) scale(1); }
|
|
100% { transform: translate(30px, -20px) scale(1.1); }
|
|
}
|
|
|
|
.particle {
|
|
position: absolute; width: var(--size, 4px); height: var(--size, 4px);
|
|
background: var(--particle-color, var(--primary)); border-radius: 50%; opacity: 0;
|
|
animation: float-up var(--float-duration, 8s) ease-in-out infinite;
|
|
animation-delay: var(--float-delay, 0s);
|
|
}
|
|
@keyframes float-up {
|
|
0% { opacity: 0; transform: translateY(100vh) scale(0); }
|
|
10% { opacity: 0.6; } 90% { opacity: 0.6; }
|
|
100% { opacity: 0; transform: translateY(-100px) scale(1); }
|
|
}
|
|
|
|
.login-container { position: relative; z-index: 10; display: flex; flex-direction: column; align-items: center; gap: 2rem; }
|
|
.login-card {
|
|
width: 420px; background: rgba(255,255,255,0.65); backdrop-filter: blur(30px) saturate(1.5);
|
|
border: 1px solid rgba(108,92,231,0.12); border-radius: 24px; padding: 3rem;
|
|
box-shadow: 0 8px 40px rgba(0,0,0,0.06), 0 0 0 1px rgba(255,255,255,0.3) inset;
|
|
animation: card-entrance 1s cubic-bezier(0.16, 1, 0.3, 1) 0.3s both;
|
|
}
|
|
.dark .login-card {
|
|
background: rgba(19,18,42,0.65); border: 1px solid rgba(162,155,254,0.15);
|
|
box-shadow: 0 8px 40px rgba(0,0,0,0.5), var(--glow-sm), 0 0 0 1px rgba(162,155,254,0.05) inset;
|
|
}
|
|
@keyframes card-entrance { from { opacity: 0; transform: translateY(40px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
|
|
|
.login-logo { text-align: center; margin-bottom: 0.25rem; animation: logo-entrance 1.2s cubic-bezier(0.16, 1, 0.3, 1) 0.5s both; }
|
|
.login-logo h1 {
|
|
font-size: 2.2rem; font-weight: 900; letter-spacing: -0.04em;
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--accent-cyan) 60%, var(--accent-pink) 100%);
|
|
background-size: 200% 200%; -webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
background-clip: text; animation: gradient-shift 4s ease-in-out infinite;
|
|
}
|
|
@keyframes logo-entrance { from { opacity: 0; transform: translateY(-20px); filter: blur(10px); } to { opacity: 1; transform: translateY(0); filter: blur(0); } }
|
|
@keyframes gradient-shift { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
|
|
|
|
.login-subtitle { text-align: center; font-size: 0.85rem; color: var(--text-muted); margin-bottom: 2rem; animation: fade-in 0.8s ease 0.7s both; }
|
|
|
|
.form-group { margin-bottom: 1.25rem; animation: form-stagger 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; }
|
|
.form-group:nth-child(1) { animation-delay: 0.8s; }
|
|
.form-group:nth-child(2) { animation-delay: 0.9s; }
|
|
@keyframes form-stagger { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } }
|
|
|
|
.form-label { display: block; font-size: 0.75rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.5rem; }
|
|
.form-input {
|
|
width: 100%; height: 48px; padding: 0 1rem; border: 1.5px solid var(--border); border-radius: 14px;
|
|
background: var(--surface); color: var(--text); font-size: 0.875rem; font-family: inherit; outline: none;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
.form-input::placeholder { color: var(--text-muted); }
|
|
.form-input:focus { border-color: var(--primary); box-shadow: 0 0 0 4px var(--primary-glow), var(--glow-sm); }
|
|
|
|
.password-wrapper { position: relative; }
|
|
.password-toggle {
|
|
position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
|
|
background: none; border: none; color: var(--text-muted); cursor: pointer; padding: 4px; transition: color 0.2s;
|
|
}
|
|
.password-toggle:hover { color: var(--primary); }
|
|
|
|
.pop-toggle {
|
|
display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem;
|
|
background: var(--bg-subtle); border-radius: 12px; margin-bottom: 1.25rem;
|
|
animation: form-stagger 0.6s cubic-bezier(0.16, 1, 0.3, 1) 1.05s both;
|
|
}
|
|
.pop-label { font-size: 0.8rem; font-weight: 500; color: var(--text-secondary); }
|
|
.toggle-switch {
|
|
width: 44px; height: 24px; background: var(--border); border-radius: 12px;
|
|
position: relative; cursor: pointer; transition: background 0.3s;
|
|
}
|
|
.toggle-switch.active { background: var(--primary); box-shadow: 0 0 12px var(--primary-glow); }
|
|
.toggle-switch::after {
|
|
content: ''; position: absolute; width: 18px; height: 18px; background: white; border-radius: 50%;
|
|
top: 3px; left: 3px; transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
|
}
|
|
.toggle-switch.active::after { transform: translateX(20px); }
|
|
|
|
.login-btn {
|
|
width: 100%; height: 52px; border: none; border-radius: 14px; font-size: 0.95rem; font-weight: 700;
|
|
font-family: inherit; color: white; cursor: pointer; position: relative; overflow: hidden;
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
|
|
box-shadow: 0 4px 20px var(--primary-glow);
|
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
animation: form-stagger 0.6s cubic-bezier(0.16, 1, 0.3, 1) 1.15s both;
|
|
}
|
|
.login-btn:hover { transform: translateY(-2px); box-shadow: var(--glow-lg), 0 8px 30px var(--primary-glow); }
|
|
.login-btn:active { transform: translateY(0) scale(0.98); }
|
|
.login-btn::before {
|
|
content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
|
animation: shimmer 3s ease-in-out infinite;
|
|
}
|
|
@keyframes shimmer { 0% { left: -100%; } 50% { left: 100%; } 100% { left: 100%; } }
|
|
|
|
.ripple {
|
|
position: absolute; border-radius: 50%; background: rgba(255,255,255,0.4);
|
|
transform: scale(0); animation: ripple-expand 0.6s ease-out; pointer-events: none;
|
|
}
|
|
@keyframes ripple-expand { to { transform: scale(4); opacity: 0; } }
|
|
|
|
.login-footer { text-align: center; margin-top: 1.5rem; font-size: 0.75rem; color: var(--text-muted); animation: fade-in 0.8s ease 1.3s both; }
|
|
.login-footer a { color: var(--primary); text-decoration: none; font-weight: 600; }
|
|
|
|
.spinner { display: inline-block; width: 20px; height: 20px; border: 2.5px solid rgba(255,255,255,0.3); border-top-color: white; border-radius: 50%; animation: spin 0.6s linear infinite; }
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
.transition-overlay {
|
|
position: fixed; inset: 0; z-index: 9999; background: var(--primary);
|
|
transform: scaleX(0); transform-origin: left; pointer-events: none;
|
|
}
|
|
.transition-overlay.active {
|
|
animation: wipe-in 0.5s cubic-bezier(0.77, 0, 0.175, 1) forwards,
|
|
wipe-out 0.5s cubic-bezier(0.77, 0, 0.175, 1) 0.5s forwards;
|
|
}
|
|
@keyframes wipe-in { to { transform: scaleX(1); } }
|
|
@keyframes wipe-out { from { transform: scaleX(1); transform-origin: right; } to { transform: scaleX(0); transform-origin: right; } }
|
|
|
|
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
|
|
/* ============================================
|
|
MAIN SCREEN — DATA DENSE TOSS STYLE
|
|
============================================ */
|
|
#main-screen { display: flex; flex-direction: column; background: var(--bg); }
|
|
|
|
/* Header — compact */
|
|
.main-header {
|
|
height: var(--header-height); display: flex; align-items: center; justify-content: space-between;
|
|
padding: 0 1.25rem; background: var(--surface); border-bottom: 1px solid var(--border);
|
|
position: relative; z-index: 50; backdrop-filter: blur(12px);
|
|
}
|
|
.dark .main-header { background: rgba(19,18,42,0.85); }
|
|
.header-left { display: flex; align-items: center; gap: 1rem; }
|
|
.header-logo {
|
|
font-size: 1rem; font-weight: 900; letter-spacing: -0.03em;
|
|
background: linear-gradient(135deg, var(--primary), var(--accent-cyan));
|
|
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
|
}
|
|
.header-breadcrumb { font-size: 0.75rem; color: var(--text-muted); }
|
|
.header-breadcrumb span { color: var(--text); font-weight: 500; }
|
|
.header-right { display: flex; align-items: center; gap: 0.75rem; }
|
|
|
|
.theme-pill {
|
|
display: flex; background: var(--bg-subtle); border: 1px solid var(--border);
|
|
border-radius: 999px; padding: 2px; gap: 2px;
|
|
}
|
|
.theme-pill button {
|
|
padding: 0.25rem 0.7rem; border-radius: 999px; border: none; background: transparent;
|
|
color: var(--text-muted); cursor: pointer; font-size: 0.65rem; font-weight: 600;
|
|
font-family: inherit; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
.theme-pill button.active { background: var(--primary); color: white; box-shadow: var(--glow-sm); }
|
|
|
|
.notif-btn {
|
|
position: relative; width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--border);
|
|
background: var(--surface); color: var(--text-secondary); cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center; transition: all 0.2s;
|
|
}
|
|
.notif-btn:hover { border-color: var(--primary); color: var(--primary); }
|
|
.notif-dot {
|
|
position: absolute; top: 5px; right: 5px; width: 7px; height: 7px;
|
|
background: var(--destructive); border-radius: 50%; animation: pulse-dot 2s ease-in-out infinite;
|
|
}
|
|
@keyframes pulse-dot { 0%, 100% { box-shadow: 0 0 0 0 rgba(255,71,87,0.4); } 50% { box-shadow: 0 0 0 5px rgba(255,71,87,0); } }
|
|
|
|
.user-avatar {
|
|
width: 32px; height: 32px; border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--primary), var(--accent-cyan));
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 0.7rem; font-weight: 700; color: white; cursor: pointer;
|
|
transition: transform 0.2s, box-shadow 0.3s;
|
|
}
|
|
.user-avatar:hover { transform: scale(1.1); box-shadow: var(--glow-sm); }
|
|
|
|
/* Tab Bar — compact */
|
|
.tab-bar {
|
|
height: var(--tabbar-height); display: flex; align-items: stretch;
|
|
background: var(--bg-subtle); border-bottom: 1px solid var(--border);
|
|
padding: 0 0.5rem; gap: 1px; overflow-x: auto;
|
|
}
|
|
.tab-item {
|
|
display: flex; align-items: center; gap: 0.4rem; padding: 0 0.85rem;
|
|
font-size: 0.7rem; font-weight: 500; color: var(--text-muted); cursor: pointer;
|
|
border-bottom: 2px solid transparent; white-space: nowrap; transition: all 0.25s;
|
|
}
|
|
.tab-item:hover { color: var(--text-secondary); background: var(--surface-hover); }
|
|
.tab-item.active { color: var(--primary); font-weight: 600; border-bottom-color: var(--primary); background: var(--surface); }
|
|
.tab-close {
|
|
width: 14px; height: 14px; border-radius: 3px; border: none; background: transparent;
|
|
color: var(--text-muted); font-size: 0.6rem; cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center; opacity: 0; transition: all 0.15s;
|
|
}
|
|
.tab-item:hover .tab-close { opacity: 1; }
|
|
.tab-close:hover { background: rgba(255,71,87,0.15); color: var(--destructive); }
|
|
|
|
/* Body */
|
|
.main-body { display: flex; flex: 1; overflow: hidden; }
|
|
|
|
/* Sidebar — compact */
|
|
.sidebar {
|
|
width: var(--sidebar-width); background: var(--sidebar-bg); border-right: 1px solid var(--border);
|
|
padding: 0.75rem 0.5rem; overflow-y: auto; display: flex; flex-direction: column; gap: 1px;
|
|
transition: background 0.6s;
|
|
}
|
|
.sidebar-section {
|
|
font-size: 0.55rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em;
|
|
color: var(--text-muted); padding: 0.85rem 0.65rem 0.35rem;
|
|
}
|
|
.sidebar-section:first-child { padding-top: 0.25rem; }
|
|
.sidebar-item {
|
|
padding: 0.45rem 0.65rem; border-radius: 8px; font-size: 0.75rem; color: var(--text-secondary);
|
|
cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); font-weight: 450;
|
|
display: flex; align-items: center; gap: 0.55rem; position: relative; overflow: hidden;
|
|
}
|
|
.sidebar-item .icon { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; opacity: 0.7; flex-shrink: 0; }
|
|
.sidebar-item:hover { background: var(--surface-hover); color: var(--text); transform: translateX(2px); }
|
|
.sidebar-item.active {
|
|
background: linear-gradient(135deg, rgba(108,92,231,0.1), rgba(108,92,231,0.05));
|
|
color: var(--primary); font-weight: 600; border: 1px solid rgba(108,92,231,0.15);
|
|
}
|
|
.sidebar-item.active .icon { opacity: 1; }
|
|
.dark .sidebar-item.active { background: linear-gradient(135deg, rgba(162,155,254,0.12), rgba(162,155,254,0.05)); border-color: rgba(162,155,254,0.15); }
|
|
.sidebar-item::before {
|
|
content: ''; position: absolute; left: 0; top: 0; width: 3px; height: 100%;
|
|
background: var(--primary); border-radius: 0 2px 2px 0; transform: scaleY(0);
|
|
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
.sidebar-item.active::before { transform: scaleY(1); }
|
|
|
|
/* Sidebar animate */
|
|
.sidebar-animate .sidebar-item { animation: slide-right 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; }
|
|
@keyframes slide-right { from { opacity: 0; transform: translateX(-16px); } to { opacity: 1; transform: translateX(0); } }
|
|
|
|
/* ===== Content Area — DENSE ===== */
|
|
.content-area {
|
|
flex: 1; overflow-y: auto; padding: 1rem 1.25rem; background: var(--bg);
|
|
display: flex; flex-direction: column; gap: 0.85rem;
|
|
}
|
|
|
|
/* Greeting — compact inline */
|
|
.greeting-row {
|
|
display: flex; justify-content: space-between; align-items: center;
|
|
}
|
|
.greeting-row h2 { font-size: 1.05rem; font-weight: 800; letter-spacing: -0.02em; }
|
|
.greeting-row .date { font-size: 0.75rem; color: var(--text-muted); }
|
|
|
|
/* ===== Stats Row — 4 compact cards ===== */
|
|
.stats-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.6rem; }
|
|
.stat-card {
|
|
background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
|
|
padding: 0.85rem 1rem; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
cursor: default; position: relative; overflow: hidden;
|
|
}
|
|
.stat-card::after {
|
|
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
|
|
background: linear-gradient(90deg, var(--primary), var(--accent-cyan));
|
|
transform: scaleX(0); transform-origin: left; transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
.stat-card:hover { transform: translateY(-2px); box-shadow: var(--card-shadow), var(--glow-sm); }
|
|
.stat-card:hover::after { transform: scaleX(1); }
|
|
.stat-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.4rem; }
|
|
.stat-label { font-size: 0.7rem; color: var(--text-muted); font-weight: 500; }
|
|
.stat-badge {
|
|
font-size: 0.55rem; font-weight: 700; padding: 0.1rem 0.4rem; border-radius: 999px;
|
|
}
|
|
.stat-badge.up { background: rgba(0,184,148,0.12); color: var(--success); }
|
|
.stat-badge.down { background: rgba(255,71,87,0.1); color: var(--destructive); }
|
|
.stat-value {
|
|
font-size: 1.4rem; font-weight: 800; letter-spacing: -0.03em;
|
|
background: linear-gradient(135deg, var(--primary), var(--accent-cyan));
|
|
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
|
}
|
|
/* Mini sparkline */
|
|
.sparkline { display: flex; align-items: flex-end; gap: 2px; height: 24px; margin-top: 0.4rem; }
|
|
.spark-bar {
|
|
flex: 1; border-radius: 2px; background: var(--primary); opacity: 0.2;
|
|
transition: all 0.3s; min-width: 3px;
|
|
}
|
|
.stat-card:hover .spark-bar { opacity: 0.5; }
|
|
.spark-bar:last-child { opacity: 0.7; background: var(--accent-cyan); }
|
|
|
|
/* ===== Middle Section — 3 column dense widgets ===== */
|
|
.widgets-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.6rem; }
|
|
|
|
.widget {
|
|
background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
|
|
overflow: hidden; transition: all 0.3s;
|
|
}
|
|
.widget:hover { box-shadow: var(--card-shadow); }
|
|
.widget-header {
|
|
display: flex; justify-content: space-between; align-items: center;
|
|
padding: 0.65rem 0.85rem; border-bottom: 1px solid var(--border-subtle);
|
|
}
|
|
.widget-title { font-size: 0.75rem; font-weight: 700; }
|
|
.widget-more {
|
|
font-size: 0.6rem; color: var(--primary); font-weight: 600; cursor: pointer;
|
|
text-decoration: none; transition: opacity 0.2s;
|
|
}
|
|
.widget-more:hover { opacity: 0.7; }
|
|
.widget-body { padding: 0.5rem 0; }
|
|
|
|
/* Approval widget - compact list */
|
|
.approval-item {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 0.45rem 0.85rem; transition: background 0.15s; cursor: pointer;
|
|
}
|
|
.approval-item:hover { background: var(--surface-hover); }
|
|
.approval-left { display: flex; align-items: center; gap: 0.5rem; flex: 1; min-width: 0; }
|
|
.approval-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
|
|
.approval-dot.pending { background: var(--warning); box-shadow: 0 0 6px rgba(253,203,110,0.4); }
|
|
.approval-dot.urgent { background: var(--destructive); box-shadow: 0 0 6px rgba(255,71,87,0.4); animation: pulse-dot 2s infinite; }
|
|
.approval-dot.done { background: var(--success); }
|
|
.approval-text {
|
|
font-size: 0.72rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
}
|
|
.approval-time { font-size: 0.6rem; color: var(--text-muted); flex-shrink: 0; margin-left: 0.5rem; }
|
|
|
|
/* Activity feed */
|
|
.activity-item {
|
|
display: flex; gap: 0.5rem; padding: 0.4rem 0.85rem; transition: background 0.15s;
|
|
}
|
|
.activity-item:hover { background: var(--surface-hover); }
|
|
.activity-avatar {
|
|
width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 0.55rem; font-weight: 700; color: white;
|
|
}
|
|
.activity-content { flex: 1; min-width: 0; }
|
|
.activity-text { font-size: 0.7rem; line-height: 1.4; }
|
|
.activity-text strong { font-weight: 600; }
|
|
.activity-time { font-size: 0.58rem; color: var(--text-muted); margin-top: 0.1rem; }
|
|
|
|
/* Device status donut */
|
|
.donut-container { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem 0.85rem; }
|
|
.donut-chart {
|
|
width: 80px; height: 80px; border-radius: 50%; position: relative;
|
|
background: conic-gradient(
|
|
var(--success) 0deg 194deg,
|
|
var(--warning) 194deg 248deg,
|
|
var(--primary) 248deg 302deg,
|
|
var(--destructive) 302deg 330deg,
|
|
var(--accent-cyan) 330deg 360deg
|
|
);
|
|
animation: donut-spin 1s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
}
|
|
@keyframes donut-spin { from { transform: rotate(-90deg); opacity: 0; } to { transform: rotate(0); opacity: 1; } }
|
|
.donut-hole {
|
|
position: absolute; inset: 14px; border-radius: 50%; background: var(--surface);
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
}
|
|
.donut-number { font-size: 1rem; font-weight: 800; letter-spacing: -0.02em; }
|
|
.donut-sub { font-size: 0.5rem; color: var(--text-muted); }
|
|
.donut-legend { display: flex; flex-direction: column; gap: 0.3rem; flex: 1; }
|
|
.legend-item { display: flex; align-items: center; gap: 0.4rem; font-size: 0.65rem; }
|
|
.legend-dot { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; }
|
|
.legend-value { margin-left: auto; font-weight: 700; font-size: 0.7rem; }
|
|
|
|
/* ===== Table — takes remaining space ===== */
|
|
.table-section {
|
|
background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
|
|
overflow: hidden; flex: 1; display: flex; flex-direction: column; min-height: 200px;
|
|
}
|
|
.table-toolbar {
|
|
display: flex; justify-content: space-between; align-items: center;
|
|
padding: 0.65rem 1rem; border-bottom: 1px solid var(--border);
|
|
}
|
|
.table-title { font-size: 0.8rem; font-weight: 700; }
|
|
.toolbar-actions { display: flex; gap: 0.4rem; }
|
|
.toolbar-btn {
|
|
height: 28px; padding: 0 0.65rem; border-radius: 8px; border: 1px solid var(--border);
|
|
background: var(--surface); color: var(--text-secondary); font-size: 0.65rem; font-weight: 500;
|
|
font-family: inherit; cursor: pointer; display: flex; align-items: center; gap: 0.3rem; transition: all 0.2s;
|
|
}
|
|
.toolbar-btn:hover { border-color: var(--primary); color: var(--primary); }
|
|
.toolbar-btn.primary { background: var(--primary); color: white; border-color: var(--primary); }
|
|
.toolbar-btn.primary:hover { box-shadow: var(--glow-sm); transform: translateY(-1px); }
|
|
|
|
.table-scroll { flex: 1; overflow-y: auto; }
|
|
table { width: 100%; border-collapse: collapse; font-size: 0.73rem; }
|
|
th {
|
|
text-align: left; padding: 0.55rem 1rem; background: var(--bg-subtle); color: var(--text-muted);
|
|
font-weight: 600; font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.06em;
|
|
border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 1;
|
|
}
|
|
td { padding: 0.5rem 1rem; border-bottom: 1px solid var(--border-subtle); color: var(--text); transition: background 0.15s; }
|
|
tr:last-child td { border-bottom: none; }
|
|
tr:hover td { background: var(--surface-hover); }
|
|
tr { animation: row-entrance 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; }
|
|
@keyframes row-entrance { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } }
|
|
|
|
.badge {
|
|
display: inline-flex; align-items: center; padding: 0.15rem 0.5rem;
|
|
border-radius: 999px; font-size: 0.58rem; font-weight: 600;
|
|
}
|
|
.badge-success { background: rgba(0,184,148,0.12); color: var(--success); }
|
|
.badge-warning { background: rgba(253,203,110,0.2); color: #b8860b; }
|
|
.dark .badge-warning { color: var(--warning); background: rgba(253,203,110,0.1); }
|
|
.badge-primary { background: rgba(108,92,231,0.12); color: var(--primary); }
|
|
.badge-destructive { background: rgba(255,71,87,0.12); color: var(--destructive); }
|
|
.badge-cyan { background: rgba(0,206,201,0.12); color: var(--accent-cyan); }
|
|
|
|
/* Online dot animation */
|
|
.online-pulse {
|
|
display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--success);
|
|
margin-right: 4px; animation: pulse-green 2s ease-in-out infinite;
|
|
}
|
|
@keyframes pulse-green { 0%, 100% { box-shadow: 0 0 0 0 rgba(0,184,148,0.4); } 50% { box-shadow: 0 0 0 4px rgba(0,184,148,0); } }
|
|
|
|
/* Stagger animation */
|
|
.animate-in { animation: slide-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; }
|
|
@keyframes slide-up { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
|
|
|
|
/* Toast */
|
|
.toast {
|
|
position: fixed; bottom: 1.5rem; right: 1.5rem; background: var(--surface); border: 1px solid var(--border);
|
|
border-radius: 14px; padding: 0.75rem 1.25rem; box-shadow: var(--card-shadow);
|
|
display: flex; align-items: center; gap: 0.65rem; z-index: 1000;
|
|
transform: translateY(120%) scale(0.9); opacity: 0;
|
|
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
|
}
|
|
.toast.show { transform: translateY(0) scale(1); opacity: 1; }
|
|
.toast-bar { width: 3px; height: 30px; border-radius: 2px; background: var(--success); }
|
|
.toast-text { font-size: 0.75rem; font-weight: 600; }
|
|
.toast-sub { font-size: 0.65rem; color: var(--text-muted); margin-top: 0.1rem; }
|
|
|
|
.preview-banner {
|
|
position: fixed; top: 0; left: 50%; transform: translateX(-50%); z-index: 10000;
|
|
background: linear-gradient(135deg, var(--primary), var(--accent-cyan));
|
|
color: white; font-size: 0.6rem; font-weight: 600; padding: 0.2rem 1.25rem;
|
|
border-radius: 0 0 10px 10px; letter-spacing: 0.05em; box-shadow: 0 4px 15px var(--primary-glow);
|
|
}
|
|
|
|
/* Ambient glow on main page (decorative) */
|
|
.ambient-glow {
|
|
position: fixed; pointer-events: none; z-index: 0; border-radius: 50%; filter: blur(150px); opacity: 0.5;
|
|
}
|
|
.ambient-glow-1 {
|
|
width: 400px; height: 400px; top: -100px; right: -100px;
|
|
background: radial-gradient(circle, rgba(108,92,231,0.06) 0%, transparent 70%);
|
|
animation: nebula-drift 20s ease-in-out infinite alternate;
|
|
}
|
|
.ambient-glow-2 {
|
|
width: 300px; height: 300px; bottom: -50px; left: 20%;
|
|
background: radial-gradient(circle, rgba(0,206,201,0.04) 0%, transparent 70%);
|
|
animation: nebula-drift 15s ease-in-out infinite alternate-reverse;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="preview-banner">INVION DESIGN PREVIEW</div>
|
|
<div class="transition-overlay" id="transition"></div>
|
|
|
|
<!-- ===== LOGIN ===== -->
|
|
<div class="screen" id="login-screen">
|
|
<div class="starfield" id="starfield"></div>
|
|
<div class="nebula nebula-1"></div>
|
|
<div class="nebula nebula-2"></div>
|
|
<div class="nebula nebula-3"></div>
|
|
<div id="particles"></div>
|
|
<div class="login-container">
|
|
<div class="login-card">
|
|
<div class="login-logo"><h1>INVION</h1></div>
|
|
<div class="login-subtitle">Welcome to the cosmos</div>
|
|
<div class="form-group">
|
|
<label class="form-label">User ID</label>
|
|
<input class="form-input" type="text" value="admin" id="login-id">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Password</label>
|
|
<div class="password-wrapper">
|
|
<input class="form-input" type="password" value="invion2024" id="login-pw">
|
|
<button class="password-toggle" onclick="togglePassword()">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="pop-toggle">
|
|
<span class="pop-label">POP Mode</span>
|
|
<div class="toggle-switch" onclick="this.classList.toggle('active')"></div>
|
|
</div>
|
|
<button class="login-btn" id="login-btn" onclick="handleLogin()">Sign In</button>
|
|
<div class="login-footer">Powered by <a href="#">INVION</a></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ===== MAIN ===== -->
|
|
<div class="screen hidden" id="main-screen">
|
|
<!-- Ambient decorative glow -->
|
|
<div class="ambient-glow ambient-glow-1"></div>
|
|
<div class="ambient-glow ambient-glow-2"></div>
|
|
|
|
<header class="main-header">
|
|
<div class="header-left">
|
|
<div class="header-logo">INVION</div>
|
|
<div class="header-breadcrumb">홈 <span>›</span> <span>대시보드</span></div>
|
|
</div>
|
|
<div class="header-right">
|
|
<div class="theme-pill">
|
|
<button class="active" onclick="setTheme('light')">Light</button>
|
|
<button onclick="setTheme('dark')">Dark</button>
|
|
</div>
|
|
<button class="notif-btn">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
|
<div class="notif-dot"></div>
|
|
</button>
|
|
<div class="user-avatar">P</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="tab-bar">
|
|
<div class="tab-item active"><span>대시보드</span><button class="tab-close">×</button></div>
|
|
<div class="tab-item"><span>DTG 관리</span><button class="tab-close">×</button></div>
|
|
<div class="tab-item"><span>사용자 관리</span><button class="tab-close">×</button></div>
|
|
<div class="tab-item"><span>정산</span><button class="tab-close">×</button></div>
|
|
</div>
|
|
|
|
<div class="main-body">
|
|
<nav class="sidebar sidebar-animate" id="sidebar">
|
|
<div class="sidebar-section">관리</div>
|
|
<div class="sidebar-item active">
|
|
<span class="icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg></span>
|
|
대시보드
|
|
</div>
|
|
<div class="sidebar-item">
|
|
<span class="icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg></span>
|
|
DTG 관리
|
|
</div>
|
|
<div class="sidebar-item">
|
|
<span class="icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg></span>
|
|
정산
|
|
</div>
|
|
<div class="sidebar-item">
|
|
<span class="icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg></span>
|
|
물류
|
|
</div>
|
|
<div class="sidebar-section">설정</div>
|
|
<div class="sidebar-item">
|
|
<span class="icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg></span>
|
|
메뉴 관리
|
|
</div>
|
|
<div class="sidebar-item">
|
|
<span class="icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg></span>
|
|
사용자 관리
|
|
</div>
|
|
<div class="sidebar-item">
|
|
<span class="icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg></span>
|
|
화면 빌더
|
|
</div>
|
|
<div class="sidebar-section">분석</div>
|
|
<div class="sidebar-item">
|
|
<span class="icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg></span>
|
|
리포트
|
|
</div>
|
|
<div class="sidebar-item">
|
|
<span class="icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg></span>
|
|
AI 어시스턴트
|
|
</div>
|
|
<div class="sidebar-item">
|
|
<span class="icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></span>
|
|
감사 로그
|
|
</div>
|
|
</nav>
|
|
|
|
<main class="content-area" id="content-area">
|
|
<!-- Greeting — compact one-liner -->
|
|
<div class="greeting-row animate-in" style="animation-delay:0.05s">
|
|
<h2>Park님, 좋은 하루 되세요 👋</h2>
|
|
<div class="date">2026년 3월 30일 월요일</div>
|
|
</div>
|
|
|
|
<!-- Stats — compact with sparklines -->
|
|
<div class="stats-row animate-in" style="animation-delay:0.1s">
|
|
<div class="stat-card">
|
|
<div class="stat-top">
|
|
<div class="stat-label">전체 장비</div>
|
|
<div class="stat-badge up">+12</div>
|
|
</div>
|
|
<div class="stat-value" id="stat-1">0</div>
|
|
<div class="sparkline" id="spark-1"></div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-top">
|
|
<div class="stat-label">가동률</div>
|
|
<div class="stat-badge up">+0.3%</div>
|
|
</div>
|
|
<div class="stat-value" id="stat-2">0%</div>
|
|
<div class="sparkline" id="spark-2"></div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-top">
|
|
<div class="stat-label">활성 세션</div>
|
|
<div class="stat-badge down">-8</div>
|
|
</div>
|
|
<div class="stat-value" id="stat-3">0</div>
|
|
<div class="sparkline" id="spark-3"></div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-top">
|
|
<div class="stat-label">평균 지연</div>
|
|
<div class="stat-badge up">-3ms</div>
|
|
</div>
|
|
<div class="stat-value" id="stat-4">0ms</div>
|
|
<div class="sparkline" id="spark-4"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 3-column widgets — DATA dense -->
|
|
<div class="widgets-row animate-in" style="animation-delay:0.15s">
|
|
<!-- 결재 현황 -->
|
|
<div class="widget">
|
|
<div class="widget-header">
|
|
<div class="widget-title">결재함 <span style="color:var(--destructive);font-size:0.65rem;font-weight:700;margin-left:4px">3건 대기</span></div>
|
|
<a class="widget-more">전체보기</a>
|
|
</div>
|
|
<div class="widget-body">
|
|
<div class="approval-item">
|
|
<div class="approval-left">
|
|
<div class="approval-dot urgent"></div>
|
|
<div class="approval-text">DTG-078 설치 승인요청</div>
|
|
</div>
|
|
<div class="approval-time">2분 전</div>
|
|
</div>
|
|
<div class="approval-item">
|
|
<div class="approval-left">
|
|
<div class="approval-dot pending"></div>
|
|
<div class="approval-text">3월 정산 보고서 검토</div>
|
|
</div>
|
|
<div class="approval-time">14분 전</div>
|
|
</div>
|
|
<div class="approval-item">
|
|
<div class="approval-left">
|
|
<div class="approval-dot pending"></div>
|
|
<div class="approval-text">신규 사용자 계정 생성</div>
|
|
</div>
|
|
<div class="approval-time">1시간 전</div>
|
|
</div>
|
|
<div class="approval-item">
|
|
<div class="approval-left">
|
|
<div class="approval-dot done"></div>
|
|
<div class="approval-text">서버 점검 일정 확인</div>
|
|
</div>
|
|
<div class="approval-time">3시간 전</div>
|
|
</div>
|
|
<div class="approval-item">
|
|
<div class="approval-left">
|
|
<div class="approval-dot done"></div>
|
|
<div class="approval-text">DTG-065 펌웨어 업데이트</div>
|
|
</div>
|
|
<div class="approval-time">어제</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 최근 활동 -->
|
|
<div class="widget">
|
|
<div class="widget-header">
|
|
<div class="widget-title">최근 활동 <span class="online-pulse"></span></div>
|
|
<a class="widget-more">전체보기</a>
|
|
</div>
|
|
<div class="widget-body">
|
|
<div class="activity-item">
|
|
<div class="activity-avatar" style="background:var(--primary)">김</div>
|
|
<div class="activity-content">
|
|
<div class="activity-text"><strong>김대리</strong>가 DTG-003 상태를 <strong>설치중</strong>으로 변경</div>
|
|
<div class="activity-time">방금 전</div>
|
|
</div>
|
|
</div>
|
|
<div class="activity-item">
|
|
<div class="activity-avatar" style="background:var(--accent-cyan)">이</div>
|
|
<div class="activity-content">
|
|
<div class="activity-text"><strong>이과장</strong>이 정산 보고서를 <strong>제출</strong></div>
|
|
<div class="activity-time">5분 전</div>
|
|
</div>
|
|
</div>
|
|
<div class="activity-item">
|
|
<div class="activity-avatar" style="background:var(--accent-pink)">박</div>
|
|
<div class="activity-content">
|
|
<div class="activity-text"><strong>박사원</strong>이 새 메뉴 <strong>물류현황</strong>을 추가</div>
|
|
<div class="activity-time">12분 전</div>
|
|
</div>
|
|
</div>
|
|
<div class="activity-item">
|
|
<div class="activity-avatar" style="background:var(--success)">S</div>
|
|
<div class="activity-content">
|
|
<div class="activity-text"><strong>System</strong> 자동 백업 완료 (2.3GB)</div>
|
|
<div class="activity-time">30분 전</div>
|
|
</div>
|
|
</div>
|
|
<div class="activity-item">
|
|
<div class="activity-avatar" style="background:var(--warning)">최</div>
|
|
<div class="activity-content">
|
|
<div class="activity-text"><strong>최부장</strong>이 결재 <strong>3건 승인</strong></div>
|
|
<div class="activity-time">1시간 전</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 장비 현황 — donut chart -->
|
|
<div class="widget">
|
|
<div class="widget-header">
|
|
<div class="widget-title">장비 현황</div>
|
|
<a class="widget-more">상세보기</a>
|
|
</div>
|
|
<div class="widget-body">
|
|
<div class="donut-container">
|
|
<div class="donut-chart">
|
|
<div class="donut-hole">
|
|
<div class="donut-number" id="donut-total">0</div>
|
|
<div class="donut-sub">전체</div>
|
|
</div>
|
|
</div>
|
|
<div class="donut-legend">
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--success)"></div> 운행중 <span class="legend-value">672</span></div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--warning)"></div> 대기 <span class="legend-value">186</span></div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--primary)"></div> 설치중 <span class="legend-value">189</span></div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--destructive)"></div> 오프라인 <span class="legend-value">97</span></div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--accent-cyan)"></div> 동기화 <span class="legend-value">103</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table — fills rest -->
|
|
<div class="table-section animate-in" style="animation-delay:0.2s">
|
|
<div class="table-toolbar">
|
|
<div class="table-title">최근 등록 장비</div>
|
|
<div class="toolbar-actions">
|
|
<button class="toolbar-btn">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
검색
|
|
</button>
|
|
<button class="toolbar-btn">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
|
Excel
|
|
</button>
|
|
<button class="toolbar-btn primary">+ 장비 등록</button>
|
|
</div>
|
|
</div>
|
|
<div class="table-scroll">
|
|
<table>
|
|
<thead><tr><th>장비코드</th><th>시리얼</th><th>제조사</th><th>모델</th><th>지역</th><th>상태</th><th>최근 통신</th></tr></thead>
|
|
<tbody id="table-body">
|
|
<tr style="animation-delay:0.25s"><td style="font-weight:600">DTG-001</td><td>0004</td><td>LOOP</td><td>LDT400BS</td><td>서울</td><td><span class="badge badge-success"><span class="online-pulse"></span>운행중</span></td><td style="font-size:0.65rem;color:var(--text-muted)">방금 전</td></tr>
|
|
<tr style="animation-delay:0.28s"><td style="font-weight:600">DTG-002</td><td>0001</td><td>LOOP</td><td>LDT400BS</td><td>부산</td><td><span class="badge badge-warning">대기</span></td><td style="font-size:0.65rem;color:var(--text-muted)">3분 전</td></tr>
|
|
<tr style="animation-delay:0.31s"><td style="font-weight:600">DTG-003</td><td>0003</td><td>LOOP</td><td>LDT400BS</td><td>인천</td><td><span class="badge badge-primary">설치중</span></td><td style="font-size:0.65rem;color:var(--text-muted)">12분 전</td></tr>
|
|
<tr style="animation-delay:0.34s"><td style="font-weight:600">DTG-004</td><td>0005</td><td>LOOP</td><td>LDT400BS</td><td>대구</td><td><span class="badge badge-destructive">오프라인</span></td><td style="font-size:0.65rem;color:var(--text-muted)">2시간 전</td></tr>
|
|
<tr style="animation-delay:0.37s"><td style="font-weight:600">DTG-005</td><td>0007</td><td>LOOP</td><td>LDT500BS</td><td>제주</td><td><span class="badge badge-cyan">동기화</span></td><td style="font-size:0.65rem;color:var(--text-muted)">1분 전</td></tr>
|
|
<tr style="animation-delay:0.4s"><td style="font-weight:600">DTG-006</td><td>0009</td><td>LOOP</td><td>LDT500BS</td><td>광주</td><td><span class="badge badge-success"><span class="online-pulse"></span>운행중</span></td><td style="font-size:0.65rem;color:var(--text-muted)">방금 전</td></tr>
|
|
<tr style="animation-delay:0.43s"><td style="font-weight:600">DTG-007</td><td>0011</td><td>LOOP</td><td>LDT400BS</td><td>대전</td><td><span class="badge badge-success"><span class="online-pulse"></span>운행중</span></td><td style="font-size:0.65rem;color:var(--text-muted)">2분 전</td></tr>
|
|
<tr style="animation-delay:0.46s"><td style="font-weight:600">DTG-008</td><td>0013</td><td>LOOP</td><td>LDT500BS</td><td>수원</td><td><span class="badge badge-warning">대기</span></td><td style="font-size:0.65rem;color:var(--text-muted)">8분 전</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="toast" id="toast">
|
|
<div class="toast-bar"></div>
|
|
<div>
|
|
<div class="toast-text">로그인 성공</div>
|
|
<div class="toast-sub">Park님 환영합니다</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Stars
|
|
function createStars() {
|
|
const field = document.getElementById('starfield');
|
|
const colors = ['rgba(162,155,254,0.8)', 'rgba(85,239,196,0.7)', 'rgba(253,121,168,0.7)'];
|
|
for (let i = 0; i < 120; i++) {
|
|
const s = document.createElement('div');
|
|
s.className = 'star' + (Math.random() > 0.85 ? ' colored' : '');
|
|
if (s.classList.contains('colored')) s.style.setProperty('--star-color', colors[Math.floor(Math.random() * 3)]);
|
|
s.style.left = Math.random() * 100 + '%';
|
|
s.style.top = Math.random() * 100 + '%';
|
|
s.style.setProperty('--duration', (2 + Math.random() * 5) + 's');
|
|
s.style.setProperty('--delay', (Math.random() * 5) + 's');
|
|
s.style.setProperty('--max-opacity', (0.3 + Math.random() * 0.7).toString());
|
|
field.appendChild(s);
|
|
}
|
|
}
|
|
|
|
// Particles
|
|
function createParticles() {
|
|
const c = document.getElementById('particles');
|
|
const colors = ['var(--primary)', 'var(--accent-cyan)', 'var(--accent-pink)'];
|
|
for (let i = 0; i < 15; i++) {
|
|
const p = document.createElement('div');
|
|
p.className = 'particle';
|
|
p.style.left = Math.random() * 100 + '%';
|
|
p.style.setProperty('--size', (2 + Math.random() * 4) + 'px');
|
|
p.style.setProperty('--particle-color', colors[Math.floor(Math.random() * 3)]);
|
|
p.style.setProperty('--float-duration', (6 + Math.random() * 10) + 's');
|
|
p.style.setProperty('--float-delay', (Math.random() * 8) + 's');
|
|
c.appendChild(p);
|
|
}
|
|
}
|
|
|
|
// Sparklines
|
|
function createSparklines() {
|
|
const data = {
|
|
'spark-1': [40, 55, 45, 60, 50, 65, 55, 70, 60, 75, 80, 85],
|
|
'spark-2': [90, 88, 91, 89, 92, 90, 93, 91, 94, 95, 96, 98],
|
|
'spark-3': [60, 55, 70, 65, 50, 55, 45, 60, 50, 40, 45, 42],
|
|
'spark-4': [25, 20, 22, 18, 20, 15, 18, 14, 16, 13, 12, 12],
|
|
};
|
|
for (const [id, values] of Object.entries(data)) {
|
|
const el = document.getElementById(id);
|
|
const max = Math.max(...values);
|
|
values.forEach((v, i) => {
|
|
const bar = document.createElement('div');
|
|
bar.className = 'spark-bar';
|
|
bar.style.height = (v / max * 100) + '%';
|
|
bar.style.animationDelay = (i * 0.05) + 's';
|
|
el.appendChild(bar);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Theme
|
|
function setTheme(t) {
|
|
document.documentElement.classList.toggle('dark', t === 'dark');
|
|
document.querySelectorAll('.theme-pill button').forEach(b => {
|
|
b.classList.toggle('active', (t === 'dark' && b.textContent === 'Dark') || (t === 'light' && b.textContent === 'Light'));
|
|
});
|
|
}
|
|
|
|
function togglePassword() {
|
|
const pw = document.getElementById('login-pw');
|
|
pw.type = pw.type === 'password' ? 'text' : 'password';
|
|
}
|
|
|
|
function addRipple(e, el) {
|
|
const rect = el.getBoundingClientRect();
|
|
const r = document.createElement('div');
|
|
r.className = 'ripple';
|
|
const size = Math.max(rect.width, rect.height) * 2;
|
|
r.style.width = r.style.height = size + 'px';
|
|
r.style.left = (e.clientX - rect.left - size / 2) + 'px';
|
|
r.style.top = (e.clientY - rect.top - size / 2) + 'px';
|
|
el.appendChild(r);
|
|
setTimeout(() => r.remove(), 600);
|
|
}
|
|
|
|
// Count up
|
|
function countUp(id, start, end, duration, suffix) {
|
|
const el = document.getElementById(id);
|
|
const t0 = performance.now();
|
|
const isFloat = end % 1 !== 0;
|
|
function tick(now) {
|
|
const p = Math.min((now - t0) / duration, 1);
|
|
const e = 1 - Math.pow(1 - p, 3);
|
|
const v = start + (end - start) * e;
|
|
el.textContent = (isFloat ? v.toFixed(1) : Math.floor(v).toLocaleString()) + suffix;
|
|
if (p < 1) requestAnimationFrame(tick);
|
|
}
|
|
requestAnimationFrame(tick);
|
|
}
|
|
|
|
// Login
|
|
function handleLogin() {
|
|
const btn = document.getElementById('login-btn');
|
|
btn.innerHTML = '<span class="spinner"></span>';
|
|
btn.style.pointerEvents = 'none';
|
|
setTimeout(() => {
|
|
const ov = document.getElementById('transition');
|
|
ov.classList.add('active');
|
|
setTimeout(() => {
|
|
document.getElementById('login-screen').classList.add('hidden');
|
|
document.getElementById('main-screen').classList.remove('hidden');
|
|
const items = document.querySelectorAll('#sidebar .sidebar-item');
|
|
items.forEach((item, i) => { item.style.animationDelay = (0.1 + i * 0.04) + 's'; });
|
|
countUp('stat-1', 0, 1247, 1200, '');
|
|
countUp('stat-2', 0, 98.5, 1400, '%');
|
|
countUp('stat-3', 0, 342, 1000, '');
|
|
countUp('stat-4', 0, 12, 800, 'ms');
|
|
countUp('donut-total', 0, 1247, 1500, '');
|
|
createSparklines();
|
|
setTimeout(() => {
|
|
const toast = document.getElementById('toast');
|
|
toast.classList.add('show');
|
|
setTimeout(() => toast.classList.remove('show'), 3000);
|
|
}, 500);
|
|
}, 500);
|
|
setTimeout(() => ov.classList.remove('active'), 1200);
|
|
}, 1000);
|
|
}
|
|
|
|
document.getElementById('login-btn').addEventListener('mousedown', function(e) { addRipple(e, this); });
|
|
document.querySelectorAll('.sidebar-item').forEach(item => {
|
|
item.addEventListener('click', function() {
|
|
document.querySelectorAll('.sidebar-item').forEach(i => i.classList.remove('active'));
|
|
this.classList.add('active');
|
|
});
|
|
});
|
|
document.querySelectorAll('.tab-item').forEach(tab => {
|
|
tab.addEventListener('click', function() {
|
|
document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active'));
|
|
this.classList.add('active');
|
|
});
|
|
});
|
|
|
|
createStars();
|
|
createParticles();
|
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches) setTheme('dark');
|
|
</script>
|
|
</body>
|
|
</html>
|