Created
March 6, 2026 16:51
-
-
Save RajChowdhury240/762023d2c32a85249401c2f849892d7c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Nexus Dashboard</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=Manrope:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> | |
| <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script> | |
| <style> | |
| *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } | |
| :root { | |
| --bg-root: #06060C; | |
| --bg-surface: #0D0D16; | |
| --bg-card: rgba(14, 14, 24, 0.7); | |
| --bg-card-hover: rgba(20, 20, 34, 0.8); | |
| --border-subtle: rgba(255, 255, 255, 0.04); | |
| --border-glow: rgba(240, 180, 41, 0.15); | |
| --accent-amber: #F0B429; | |
| --accent-amber-dim: rgba(240, 180, 41, 0.12); | |
| --accent-teal: #2DD4BF; | |
| --accent-teal-dim: rgba(45, 212, 191, 0.1); | |
| --accent-coral: #FF6B6B; | |
| --accent-coral-dim: rgba(255, 107, 107, 0.1); | |
| --accent-violet: #A78BFA; | |
| --accent-violet-dim: rgba(167, 139, 250, 0.1); | |
| --text-primary: #E8E6F0; | |
| --text-secondary: rgba(232, 230, 240, 0.55); | |
| --text-tertiary: rgba(232, 230, 240, 0.3); | |
| --font-display: 'Syne', sans-serif; | |
| --font-body: 'Manrope', sans-serif; | |
| --font-mono: 'JetBrains Mono', monospace; | |
| --radius-sm: 8px; | |
| --radius-md: 14px; | |
| --radius-lg: 20px; | |
| } | |
| html { font-size: 15px; } | |
| body { | |
| font-family: var(--font-body); | |
| background: var(--bg-root); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* Ambient background */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| background: | |
| radial-gradient(ellipse 80% 60% at 20% 10%, rgba(240, 180, 41, 0.03) 0%, transparent 60%), | |
| radial-gradient(ellipse 60% 50% at 80% 80%, rgba(45, 212, 191, 0.02) 0%, transparent 50%), | |
| radial-gradient(ellipse 40% 40% at 50% 50%, rgba(167, 139, 250, 0.015) 0%, transparent 50%); | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| /* Noise texture overlay */ | |
| body::after { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| opacity: 0.35; | |
| background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E"); | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| #app { position: relative; z-index: 1; } | |
| /* Scrollbar */ | |
| ::-webkit-scrollbar { width: 6px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 3px; } | |
| ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.15); } | |
| /* ============ LAYOUT ============ */ | |
| .dashboard { display: flex; min-height: 100vh; } | |
| /* Sidebar */ | |
| .sidebar { | |
| width: 260px; | |
| min-height: 100vh; | |
| background: var(--bg-surface); | |
| border-right: 1px solid var(--border-subtle); | |
| padding: 28px 20px; | |
| display: flex; | |
| flex-direction: column; | |
| position: fixed; | |
| left: 0; | |
| top: 0; | |
| bottom: 0; | |
| z-index: 10; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| margin-bottom: 40px; | |
| padding: 0 4px; | |
| } | |
| .logo-icon { | |
| width: 36px; | |
| height: 36px; | |
| background: linear-gradient(135deg, var(--accent-amber), #E07C24); | |
| border-radius: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| box-shadow: 0 0 20px rgba(240, 180, 41, 0.2); | |
| } | |
| .logo-icon svg { width: 20px; height: 20px; } | |
| .logo-text { | |
| font-family: var(--font-display); | |
| font-weight: 700; | |
| font-size: 1.25rem; | |
| letter-spacing: -0.02em; | |
| } | |
| .nav-section { | |
| margin-bottom: 32px; | |
| } | |
| .nav-label { | |
| font-size: 0.65rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 0.12em; | |
| color: var(--text-tertiary); | |
| padding: 0 12px; | |
| margin-bottom: 10px; | |
| } | |
| .nav-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 10px 12px; | |
| border-radius: var(--radius-sm); | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| color: var(--text-secondary); | |
| font-size: 0.88rem; | |
| font-weight: 500; | |
| position: relative; | |
| } | |
| .nav-item:hover { | |
| background: rgba(255, 255, 255, 0.03); | |
| color: var(--text-primary); | |
| } | |
| .nav-item.active { | |
| background: var(--accent-amber-dim); | |
| color: var(--accent-amber); | |
| } | |
| .nav-item.active::before { | |
| content: ''; | |
| position: absolute; | |
| left: -20px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| width: 3px; | |
| height: 20px; | |
| background: var(--accent-amber); | |
| border-radius: 0 3px 3px 0; | |
| } | |
| .nav-item svg { width: 18px; height: 18px; opacity: 0.7; flex-shrink: 0; } | |
| .nav-item.active svg { opacity: 1; } | |
| .nav-badge { | |
| margin-left: auto; | |
| background: var(--accent-coral); | |
| color: #fff; | |
| font-size: 0.65rem; | |
| font-weight: 700; | |
| padding: 2px 7px; | |
| border-radius: 10px; | |
| font-family: var(--font-mono); | |
| } | |
| .sidebar-footer { | |
| margin-top: auto; | |
| padding: 16px 12px; | |
| border-top: 1px solid var(--border-subtle); | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .avatar { | |
| width: 34px; | |
| height: 34px; | |
| border-radius: 10px; | |
| background: linear-gradient(135deg, var(--accent-teal), var(--accent-violet)); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-family: var(--font-display); | |
| font-weight: 700; | |
| font-size: 0.8rem; | |
| color: #fff; | |
| } | |
| .user-info { flex: 1; min-width: 0; } | |
| .user-name { font-size: 0.85rem; font-weight: 600; } | |
| .user-role { font-size: 0.7rem; color: var(--text-tertiary); } | |
| /* Main content */ | |
| .main { | |
| flex: 1; | |
| margin-left: 260px; | |
| padding: 28px 32px 40px; | |
| } | |
| /* Top bar */ | |
| .topbar { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| margin-bottom: 32px; | |
| } | |
| .topbar-left h1 { | |
| font-family: var(--font-display); | |
| font-size: 1.8rem; | |
| font-weight: 800; | |
| letter-spacing: -0.03em; | |
| line-height: 1.1; | |
| } | |
| .topbar-left p { | |
| color: var(--text-secondary); | |
| font-size: 0.85rem; | |
| margin-top: 4px; | |
| } | |
| .topbar-right { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .search-box { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-subtle); | |
| border-radius: var(--radius-sm); | |
| padding: 8px 14px; | |
| width: 240px; | |
| transition: border-color 0.2s; | |
| } | |
| .search-box:focus-within { border-color: var(--border-glow); } | |
| .search-box svg { width: 16px; height: 16px; color: var(--text-tertiary); flex-shrink: 0; } | |
| .search-box input { | |
| background: none; | |
| border: none; | |
| outline: none; | |
| color: var(--text-primary); | |
| font-family: var(--font-body); | |
| font-size: 0.82rem; | |
| width: 100%; | |
| } | |
| .search-box input::placeholder { color: var(--text-tertiary); } | |
| .icon-btn { | |
| width: 38px; | |
| height: 38px; | |
| border-radius: var(--radius-sm); | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-subtle); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| position: relative; | |
| } | |
| .icon-btn:hover { background: var(--bg-card-hover); border-color: var(--border-glow); } | |
| .icon-btn svg { width: 17px; height: 17px; color: var(--text-secondary); } | |
| .icon-btn .notif-dot { | |
| position: absolute; | |
| top: 7px; | |
| right: 8px; | |
| width: 7px; | |
| height: 7px; | |
| background: var(--accent-coral); | |
| border-radius: 50%; | |
| border: 2px solid var(--bg-surface); | |
| } | |
| /* Widget Grid */ | |
| .grid { | |
| display: grid; | |
| grid-template-columns: repeat(12, 1fr); | |
| gap: 18px; | |
| } | |
| .widget { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-subtle); | |
| border-radius: var(--radius-lg); | |
| padding: 22px; | |
| backdrop-filter: blur(20px); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .widget::before { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| border-radius: var(--radius-lg); | |
| padding: 1px; | |
| background: linear-gradient(135deg, rgba(255,255,255,0.05), transparent 50%); | |
| -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); | |
| -webkit-mask-composite: xor; | |
| mask-composite: exclude; | |
| pointer-events: none; | |
| } | |
| .widget:hover { border-color: rgba(255, 255, 255, 0.06); transform: translateY(-1px); } | |
| .col-3 { grid-column: span 3; } | |
| .col-4 { grid-column: span 4; } | |
| .col-5 { grid-column: span 5; } | |
| .col-6 { grid-column: span 6; } | |
| .col-7 { grid-column: span 7; } | |
| .col-8 { grid-column: span 8; } | |
| .col-12 { grid-column: span 12; } | |
| .widget-header { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| margin-bottom: 18px; | |
| } | |
| .widget-title { | |
| font-family: var(--font-display); | |
| font-size: 0.95rem; | |
| font-weight: 700; | |
| letter-spacing: -0.01em; | |
| } | |
| .widget-subtitle { | |
| font-size: 0.72rem; | |
| color: var(--text-tertiary); | |
| margin-top: 2px; | |
| } | |
| .widget-action { | |
| font-size: 0.72rem; | |
| color: var(--accent-amber); | |
| cursor: pointer; | |
| font-weight: 600; | |
| transition: opacity 0.2s; | |
| } | |
| .widget-action:hover { opacity: 0.8; } | |
| /* ============ STAT CARDS ============ */ | |
| .stat-cards { display: flex; gap: 18px; grid-column: span 12; } | |
| .stat-card { | |
| flex: 1; | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-subtle); | |
| border-radius: var(--radius-md); | |
| padding: 20px; | |
| position: relative; | |
| overflow: hidden; | |
| transition: all 0.3s; | |
| } | |
| .stat-card:hover { border-color: rgba(255,255,255,0.06); } | |
| .stat-card-icon { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 11px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 14px; | |
| } | |
| .stat-card-icon svg { width: 20px; height: 20px; } | |
| .stat-card-icon.amber { background: var(--accent-amber-dim); color: var(--accent-amber); } | |
| .stat-card-icon.teal { background: var(--accent-teal-dim); color: var(--accent-teal); } | |
| .stat-card-icon.coral { background: var(--accent-coral-dim); color: var(--accent-coral); } | |
| .stat-card-icon.violet { background: var(--accent-violet-dim); color: var(--accent-violet); } | |
| .stat-card-value { | |
| font-family: var(--font-mono); | |
| font-size: 1.65rem; | |
| font-weight: 600; | |
| letter-spacing: -0.03em; | |
| margin-bottom: 4px; | |
| } | |
| .stat-card-label { | |
| font-size: 0.75rem; | |
| color: var(--text-secondary); | |
| } | |
| .stat-card-change { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| font-family: var(--font-mono); | |
| font-size: 0.72rem; | |
| font-weight: 600; | |
| padding: 3px 8px; | |
| border-radius: 6px; | |
| } | |
| .stat-card-change.up { background: rgba(45, 212, 191, 0.1); color: var(--accent-teal); } | |
| .stat-card-change.down { background: rgba(255, 107, 107, 0.1); color: var(--accent-coral); } | |
| /* Sparkline in stat card */ | |
| .stat-sparkline { | |
| margin-top: 12px; | |
| height: 32px; | |
| } | |
| .stat-sparkline svg { width: 100%; height: 100%; } | |
| /* ============ ANALYTICS CHART ============ */ | |
| .chart-container { height: 220px; position: relative; } | |
| .chart-tabs { | |
| display: flex; | |
| gap: 4px; | |
| background: rgba(255,255,255,0.03); | |
| border-radius: 8px; | |
| padding: 3px; | |
| } | |
| .chart-tab { | |
| font-size: 0.72rem; | |
| font-weight: 600; | |
| padding: 5px 14px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| color: var(--text-tertiary); | |
| transition: all 0.2s; | |
| } | |
| .chart-tab.active { background: var(--accent-amber-dim); color: var(--accent-amber); } | |
| .chart-tab:hover:not(.active) { color: var(--text-secondary); } | |
| .chart-svg { width: 100%; height: 100%; } | |
| .chart-grid-line { | |
| stroke: rgba(255,255,255,0.03); | |
| stroke-width: 1; | |
| } | |
| .chart-label { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| fill: var(--text-tertiary); | |
| } | |
| .chart-line { | |
| fill: none; | |
| stroke-width: 2.5; | |
| stroke-linecap: round; | |
| stroke-linejoin: round; | |
| } | |
| .chart-area { opacity: 0.15; } | |
| .chart-dot { | |
| stroke-width: 2; | |
| transition: r 0.2s; | |
| cursor: pointer; | |
| } | |
| .chart-dot:hover { r: 6; } | |
| /* ============ SYSTEM STATS ============ */ | |
| .system-stats { display: flex; flex-direction: column; gap: 20px; } | |
| .gauge-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| } | |
| .gauge-ring { | |
| width: 52px; | |
| height: 52px; | |
| flex-shrink: 0; | |
| position: relative; | |
| } | |
| .gauge-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); } | |
| .gauge-bg { | |
| fill: none; | |
| stroke: rgba(255,255,255,0.04); | |
| stroke-width: 4; | |
| } | |
| .gauge-fill { | |
| fill: none; | |
| stroke-width: 4; | |
| stroke-linecap: round; | |
| transition: stroke-dashoffset 1s ease; | |
| } | |
| .gauge-value { | |
| position: absolute; | |
| inset: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-family: var(--font-mono); | |
| font-size: 0.68rem; | |
| font-weight: 600; | |
| } | |
| .gauge-info { flex: 1; min-width: 0; } | |
| .gauge-label { | |
| font-size: 0.82rem; | |
| font-weight: 600; | |
| margin-bottom: 3px; | |
| } | |
| .gauge-detail { | |
| font-size: 0.7rem; | |
| color: var(--text-tertiary); | |
| font-family: var(--font-mono); | |
| } | |
| /* ============ ACTIVITY FEED ============ */ | |
| .activity-list { display: flex; flex-direction: column; gap: 2px; } | |
| .activity-item { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 12px; | |
| padding: 10px 0; | |
| border-bottom: 1px solid var(--border-subtle); | |
| position: relative; | |
| } | |
| .activity-item:last-child { border-bottom: none; } | |
| .activity-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| margin-top: 5px; | |
| flex-shrink: 0; | |
| } | |
| .activity-dot.amber { background: var(--accent-amber); box-shadow: 0 0 8px rgba(240, 180, 41, 0.3); } | |
| .activity-dot.teal { background: var(--accent-teal); box-shadow: 0 0 8px rgba(45, 212, 191, 0.3); } | |
| .activity-dot.coral { background: var(--accent-coral); box-shadow: 0 0 8px rgba(255, 107, 107, 0.3); } | |
| .activity-dot.violet { background: var(--accent-violet); box-shadow: 0 0 8px rgba(167, 139, 250, 0.3); } | |
| .activity-content { flex: 1; min-width: 0; } | |
| .activity-text { | |
| font-size: 0.82rem; | |
| line-height: 1.5; | |
| color: var(--text-secondary); | |
| } | |
| .activity-text strong { color: var(--text-primary); font-weight: 600; } | |
| .activity-time { | |
| font-size: 0.68rem; | |
| color: var(--text-tertiary); | |
| font-family: var(--font-mono); | |
| margin-top: 3px; | |
| } | |
| /* ============ TASKS ============ */ | |
| .task-list { display: flex; flex-direction: column; gap: 6px; } | |
| .task-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 10px 12px; | |
| border-radius: var(--radius-sm); | |
| transition: background 0.2s; | |
| cursor: pointer; | |
| } | |
| .task-item:hover { background: rgba(255,255,255,0.02); } | |
| .task-check { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 6px; | |
| border: 2px solid rgba(255,255,255,0.12); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| transition: all 0.2s; | |
| cursor: pointer; | |
| } | |
| .task-check.done { | |
| background: var(--accent-teal); | |
| border-color: var(--accent-teal); | |
| } | |
| .task-check.done svg { opacity: 1; } | |
| .task-check svg { width: 12px; height: 12px; opacity: 0; color: #fff; } | |
| .task-text { | |
| flex: 1; | |
| font-size: 0.82rem; | |
| font-weight: 500; | |
| } | |
| .task-text.done { | |
| text-decoration: line-through; | |
| color: var(--text-tertiary); | |
| } | |
| .task-priority { | |
| font-size: 0.62rem; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.06em; | |
| padding: 3px 8px; | |
| border-radius: 5px; | |
| } | |
| .task-priority.high { background: var(--accent-coral-dim); color: var(--accent-coral); } | |
| .task-priority.medium { background: var(--accent-amber-dim); color: var(--accent-amber); } | |
| .task-priority.low { background: var(--accent-teal-dim); color: var(--accent-teal); } | |
| /* ============ WEATHER ============ */ | |
| .weather-main { | |
| text-align: center; | |
| padding: 10px 0 16px; | |
| } | |
| .weather-icon { | |
| font-size: 3.2rem; | |
| margin-bottom: 8px; | |
| line-height: 1; | |
| } | |
| .weather-temp { | |
| font-family: var(--font-display); | |
| font-size: 2.8rem; | |
| font-weight: 800; | |
| letter-spacing: -0.04em; | |
| background: linear-gradient(135deg, var(--accent-amber), #E07C24); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .weather-desc { | |
| font-size: 0.85rem; | |
| color: var(--text-secondary); | |
| margin-top: 2px; | |
| } | |
| .weather-loc { | |
| font-size: 0.72rem; | |
| color: var(--text-tertiary); | |
| font-family: var(--font-mono); | |
| } | |
| .weather-details { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| margin-top: 16px; | |
| padding-top: 16px; | |
| border-top: 1px solid var(--border-subtle); | |
| } | |
| .weather-detail { | |
| text-align: center; | |
| } | |
| .weather-detail-val { | |
| font-family: var(--font-mono); | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| } | |
| .weather-detail-label { | |
| font-size: 0.65rem; | |
| color: var(--text-tertiary); | |
| margin-top: 2px; | |
| } | |
| /* ============ CALENDAR ============ */ | |
| .calendar-grid { | |
| display: grid; | |
| grid-template-columns: repeat(7, 1fr); | |
| gap: 4px; | |
| text-align: center; | |
| } | |
| .calendar-dow { | |
| font-size: 0.62rem; | |
| font-weight: 600; | |
| color: var(--text-tertiary); | |
| text-transform: uppercase; | |
| letter-spacing: 0.06em; | |
| padding: 4px 0 8px; | |
| } | |
| .calendar-day { | |
| aspect-ratio: 1; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.78rem; | |
| font-family: var(--font-mono); | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: all 0.15s; | |
| color: var(--text-secondary); | |
| position: relative; | |
| } | |
| .calendar-day:hover { background: rgba(255,255,255,0.04); } | |
| .calendar-day.other { color: var(--text-tertiary); opacity: 0.4; } | |
| .calendar-day.today { | |
| background: var(--accent-amber); | |
| color: #0A0A0F; | |
| font-weight: 700; | |
| box-shadow: 0 0 16px rgba(240, 180, 41, 0.3); | |
| } | |
| .calendar-day.has-event::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 3px; | |
| width: 4px; | |
| height: 4px; | |
| border-radius: 50%; | |
| background: var(--accent-teal); | |
| } | |
| .calendar-month { | |
| font-family: var(--font-display); | |
| font-size: 1rem; | |
| font-weight: 700; | |
| } | |
| .calendar-nav { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .calendar-nav button { | |
| width: 28px; | |
| height: 28px; | |
| border-radius: 7px; | |
| border: 1px solid var(--border-subtle); | |
| background: transparent; | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.15s; | |
| } | |
| .calendar-nav button:hover { background: rgba(255,255,255,0.04); } | |
| .calendar-nav button svg { width: 14px; height: 14px; } | |
| .upcoming-events { margin-top: 16px; display: flex; flex-direction: column; gap: 8px; } | |
| .event-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| padding: 8px 10px; | |
| border-radius: var(--radius-sm); | |
| background: rgba(255,255,255,0.02); | |
| } | |
| .event-bar { | |
| width: 3px; | |
| height: 32px; | |
| border-radius: 2px; | |
| flex-shrink: 0; | |
| } | |
| .event-bar.amber { background: var(--accent-amber); } | |
| .event-bar.teal { background: var(--accent-teal); } | |
| .event-bar.violet { background: var(--accent-violet); } | |
| .event-info { flex: 1; } | |
| .event-name { font-size: 0.78rem; font-weight: 600; } | |
| .event-time { | |
| font-size: 0.65rem; | |
| color: var(--text-tertiary); | |
| font-family: var(--font-mono); | |
| } | |
| /* ============ ANIMATIONS ============ */ | |
| @keyframes fadeInUp { | |
| from { opacity: 0; transform: translateY(16px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| @keyframes slideInLeft { | |
| from { opacity: 0; transform: translateX(-20px); } | |
| to { opacity: 1; transform: translateX(0); } | |
| } | |
| @keyframes pulseGlow { | |
| 0%, 100% { box-shadow: 0 0 8px rgba(240, 180, 41, 0.15); } | |
| 50% { box-shadow: 0 0 16px rgba(240, 180, 41, 0.3); } | |
| } | |
| @keyframes drawLine { | |
| from { stroke-dashoffset: 1000; } | |
| to { stroke-dashoffset: 0; } | |
| } | |
| .sidebar { animation: slideInLeft 0.5s ease both; } | |
| .stat-card { | |
| animation: fadeInUp 0.5s ease both; | |
| } | |
| .stat-card:nth-child(1) { animation-delay: 0.1s; } | |
| .stat-card:nth-child(2) { animation-delay: 0.17s; } | |
| .stat-card:nth-child(3) { animation-delay: 0.24s; } | |
| .stat-card:nth-child(4) { animation-delay: 0.31s; } | |
| .widget { | |
| animation: fadeInUp 0.5s ease both; | |
| } | |
| .grid > :nth-child(2) { animation-delay: 0.15s; } | |
| .grid > :nth-child(3) { animation-delay: 0.22s; } | |
| .grid > :nth-child(4) { animation-delay: 0.29s; } | |
| .grid > :nth-child(5) { animation-delay: 0.36s; } | |
| .grid > :nth-child(6) { animation-delay: 0.43s; } | |
| .chart-line { | |
| stroke-dasharray: 1000; | |
| animation: drawLine 1.5s ease forwards; | |
| animation-delay: 0.5s; | |
| stroke-dashoffset: 1000; | |
| } | |
| /* ============ RESPONSIVE ============ */ | |
| @media (max-width: 1200px) { | |
| .col-3 { grid-column: span 6; } | |
| .col-4 { grid-column: span 6; } | |
| .col-5 { grid-column: span 12; } | |
| .col-7 { grid-column: span 12; } | |
| .col-8 { grid-column: span 12; } | |
| } | |
| @media (max-width: 768px) { | |
| .sidebar { display: none; } | |
| .main { margin-left: 0; padding: 20px 16px; } | |
| .stat-cards { flex-direction: column; } | |
| .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-12 { grid-column: span 12; } | |
| .topbar { flex-direction: column; align-items: flex-start; gap: 16px; } | |
| .search-box { width: 100%; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="app"> | |
| <div class="dashboard"> | |
| <!-- Sidebar --> | |
| <aside class="sidebar"> | |
| <div class="logo"> | |
| <div class="logo-icon"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> | |
| <polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"/> | |
| <line x1="12" y1="22" x2="12" y2="15.5"/> | |
| <polyline points="22 8.5 12 15.5 2 8.5"/> | |
| </svg> | |
| </div> | |
| <span class="logo-text">Nexus</span> | |
| </div> | |
| <nav> | |
| <div class="nav-section"> | |
| <div class="nav-label">Overview</div> | |
| <div class="nav-item active"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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> | |
| Dashboard | |
| </div> | |
| <div class="nav-item"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.21 15.89A10 10 0 1 1 8 2.83"/><path d="M22 12A10 10 0 0 0 12 2v10z"/></svg> | |
| Analytics | |
| </div> | |
| <div class="nav-item"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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> | |
| Users | |
| <span class="nav-badge">3</span> | |
| </div> | |
| </div> | |
| <div class="nav-section"> | |
| <div class="nav-label">Workspace</div> | |
| <div class="nav-item"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg> | |
| Documents | |
| </div> | |
| <div class="nav-item"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg> | |
| Inbox | |
| <span class="nav-badge">12</span> | |
| </div> | |
| <div class="nav-item"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> | |
| Settings | |
| </div> | |
| </div> | |
| </nav> | |
| <div class="sidebar-footer"> | |
| <div class="avatar">RK</div> | |
| <div class="user-info"> | |
| <div class="user-name">Raj Kumar</div> | |
| <div class="user-role">Administrator</div> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- Main --> | |
| <main class="main"> | |
| <div class="topbar"> | |
| <div class="topbar-left"> | |
| <h1>Dashboard</h1> | |
| <p>Welcome back. Here's what's happening today.</p> | |
| </div> | |
| <div class="topbar-right"> | |
| <div class="search-box"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg> | |
| <input type="text" placeholder="Search anything..." /> | |
| </div> | |
| <div class="icon-btn"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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> | |
| <span class="notif-dot"></span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Stat cards --> | |
| <div class="stat-cards"> | |
| <div class="stat-card" v-for="(stat, i) in stats" :key="i"> | |
| <div :class="['stat-card-icon', stat.color]"> | |
| <svg v-if="stat.icon==='revenue'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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> | |
| <svg v-if="stat.icon==='users'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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> | |
| <svg v-if="stat.icon==='orders'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg> | |
| <svg v-if="stat.icon==='uptime'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg> | |
| </div> | |
| <div class="stat-card-value">{{ stat.value }}</div> | |
| <div class="stat-card-label">{{ stat.label }}</div> | |
| <div :class="['stat-card-change', stat.changeDir]">{{ stat.change }}</div> | |
| <div class="stat-sparkline"> | |
| <svg viewBox="0 0 120 32" preserveAspectRatio="none"> | |
| <polyline :points="stat.sparkline" fill="none" :stroke="stat.strokeColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| </svg> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="height: 18px"></div> | |
| <!-- Widget Grid --> | |
| <div class="grid"> | |
| <!-- Analytics Chart --> | |
| <div class="widget col-8"> | |
| <div class="widget-header"> | |
| <div> | |
| <div class="widget-title">Revenue Analytics</div> | |
| <div class="widget-subtitle">Monthly performance overview</div> | |
| </div> | |
| <div class="chart-tabs"> | |
| <div v-for="tab in ['7D','1M','3M','1Y']" :key="tab" | |
| :class="['chart-tab', { active: chartTab === tab }]" | |
| @click="chartTab = tab">{{ tab }}</div> | |
| </div> | |
| </div> | |
| <div class="chart-container"> | |
| <svg class="chart-svg" :viewBox="'0 0 ' + chartW + ' ' + chartH"> | |
| <!-- Grid lines --> | |
| <line v-for="i in 5" :key="'g'+i" class="chart-grid-line" | |
| :x1="chartPad" :x2="chartW - 10" | |
| :y1="chartPad + (i-1) * ((chartH - chartPad*2) / 4)" | |
| :y2="chartPad + (i-1) * ((chartH - chartPad*2) / 4)"/> | |
| <!-- Y labels --> | |
| <text v-for="(label, i) in yLabels" :key="'yl'+i" class="chart-label" text-anchor="end" | |
| :x="chartPad - 8" | |
| :y="chartPad + i * ((chartH - chartPad*2) / 4) + 4">{{ label }}</text> | |
| <!-- X labels --> | |
| <text v-for="(label, i) in xLabels" :key="'xl'+i" class="chart-label" text-anchor="middle" | |
| :x="chartPad + i * ((chartW - chartPad - 10) / (xLabels.length - 1))" | |
| :y="chartH - 4">{{ label }}</text> | |
| <!-- Area fill --> | |
| <path :d="areaPath" class="chart-area" :fill="'url(#areaGrad)'"/> | |
| <defs> | |
| <linearGradient id="areaGrad" x1="0" y1="0" x2="0" y2="1"> | |
| <stop offset="0%" stop-color="#F0B429" stop-opacity="0.4"/> | |
| <stop offset="100%" stop-color="#F0B429" stop-opacity="0"/> | |
| </linearGradient> | |
| <linearGradient id="lineGrad" x1="0" y1="0" x2="1" y2="0"> | |
| <stop offset="0%" stop-color="#F0B429"/> | |
| <stop offset="100%" stop-color="#E07C24"/> | |
| </linearGradient> | |
| <linearGradient id="areaGrad2" x1="0" y1="0" x2="0" y2="1"> | |
| <stop offset="0%" stop-color="#2DD4BF" stop-opacity="0.3"/> | |
| <stop offset="100%" stop-color="#2DD4BF" stop-opacity="0"/> | |
| </linearGradient> | |
| </defs> | |
| <!-- Line 2 (secondary) --> | |
| <path :d="linePath2" class="chart-line" stroke="#2DD4BF" stroke-opacity="0.5"/> | |
| <path :d="areaPath2" class="chart-area" fill="url(#areaGrad2)"/> | |
| <!-- Line 1 (primary) --> | |
| <path :d="linePath" class="chart-line" stroke="url(#lineGrad)"/> | |
| <!-- Dots --> | |
| <circle v-for="(pt, i) in chartPoints" :key="'d'+i" | |
| class="chart-dot" :cx="pt.x" :cy="pt.y" r="4" | |
| fill="#0A0A0F" stroke="#F0B429" /> | |
| </svg> | |
| </div> | |
| </div> | |
| <!-- System Stats --> | |
| <div class="widget col-4"> | |
| <div class="widget-header"> | |
| <div> | |
| <div class="widget-title">System Health</div> | |
| <div class="widget-subtitle">Server performance metrics</div> | |
| </div> | |
| <div class="widget-action">Details</div> | |
| </div> | |
| <div class="system-stats"> | |
| <div class="gauge-row" v-for="(g, i) in gauges" :key="i"> | |
| <div class="gauge-ring"> | |
| <svg viewBox="0 0 48 48"> | |
| <circle class="gauge-bg" cx="24" cy="24" r="20"/> | |
| <circle class="gauge-fill" cx="24" cy="24" r="20" | |
| :stroke="g.color" | |
| :stroke-dasharray="125.6" | |
| :stroke-dashoffset="125.6 - (125.6 * g.value / 100)"/> | |
| </svg> | |
| <div class="gauge-value" :style="{ color: g.color }">{{ g.value }}%</div> | |
| </div> | |
| <div class="gauge-info"> | |
| <div class="gauge-label">{{ g.label }}</div> | |
| <div class="gauge-detail">{{ g.detail }}</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Activity Feed --> | |
| <div class="widget col-5"> | |
| <div class="widget-header"> | |
| <div> | |
| <div class="widget-title">Activity Feed</div> | |
| <div class="widget-subtitle">Recent events and updates</div> | |
| </div> | |
| <div class="widget-action">View all</div> | |
| </div> | |
| <div class="activity-list"> | |
| <div class="activity-item" v-for="(a, i) in activities" :key="i"> | |
| <div :class="['activity-dot', a.color]"></div> | |
| <div class="activity-content"> | |
| <div class="activity-text" v-html="a.text"></div> | |
| <div class="activity-time">{{ a.time }}</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Tasks --> | |
| <div class="widget col-4"> | |
| <div class="widget-header"> | |
| <div> | |
| <div class="widget-title">Tasks</div> | |
| <div class="widget-subtitle">{{ completedTasks }}/{{ tasks.length }} completed</div> | |
| </div> | |
| <div class="widget-action">+ Add</div> | |
| </div> | |
| <div class="task-list"> | |
| <div class="task-item" v-for="(t, i) in tasks" :key="i" @click="t.done = !t.done"> | |
| <div :class="['task-check', { done: t.done }]"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> | |
| </div> | |
| <div :class="['task-text', { done: t.done }]">{{ t.text }}</div> | |
| <div :class="['task-priority', t.priority]">{{ t.priority }}</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Weather --> | |
| <div class="widget col-3"> | |
| <div class="widget-header"> | |
| <div class="widget-title">Weather</div> | |
| </div> | |
| <div class="weather-main"> | |
| <div class="weather-icon">{{ weather.icon }}</div> | |
| <div class="weather-temp">{{ weather.temp }}</div> | |
| <div class="weather-desc">{{ weather.desc }}</div> | |
| <div class="weather-loc">{{ weather.location }}</div> | |
| </div> | |
| <div class="weather-details"> | |
| <div class="weather-detail" v-for="(d, i) in weather.details" :key="i"> | |
| <div class="weather-detail-val">{{ d.value }}</div> | |
| <div class="weather-detail-label">{{ d.label }}</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Calendar --> | |
| <div class="widget col-5"> | |
| <div class="widget-header"> | |
| <div class="calendar-month">{{ monthName }} {{ year }}</div> | |
| <div class="calendar-nav"> | |
| <button @click="prevMonth"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg> | |
| </button> | |
| <button @click="nextMonth"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="calendar-grid"> | |
| <div class="calendar-dow" v-for="d in dows" :key="d">{{ d }}</div> | |
| <div v-for="(day, i) in calendarDays" :key="i" | |
| :class="['calendar-day', { | |
| other: day.other, | |
| today: day.isToday, | |
| 'has-event': day.hasEvent | |
| }]">{{ day.num }}</div> | |
| </div> | |
| <div class="upcoming-events"> | |
| <div class="event-item" v-for="(e, i) in events" :key="i"> | |
| <div :class="['event-bar', e.color]"></div> | |
| <div class="event-info"> | |
| <div class="event-name">{{ e.name }}</div> | |
| <div class="event-time">{{ e.time }}</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Quick Chart - Bar chart --> | |
| <div class="widget col-7"> | |
| <div class="widget-header"> | |
| <div> | |
| <div class="widget-title">Traffic Sources</div> | |
| <div class="widget-subtitle">Visitors by channel this week</div> | |
| </div> | |
| <div class="widget-action">Export</div> | |
| </div> | |
| <div style="display: flex; gap: 30px; align-items: flex-end; height: 160px; padding-top: 10px;"> | |
| <div v-for="(bar, i) in trafficBars" :key="i" | |
| style="flex: 1; display: flex; flex-direction: column; align-items: center; gap: 8px;"> | |
| <div style="font-family: var(--font-mono); font-size: 0.68rem; color: var(--text-secondary);"> | |
| {{ bar.value }} | |
| </div> | |
| <div :style="{ | |
| width: '100%', | |
| maxWidth: '48px', | |
| height: bar.height + '%', | |
| background: 'linear-gradient(to top, ' + bar.colorFrom + ', ' + bar.colorTo + ')', | |
| borderRadius: '6px 6px 4px 4px', | |
| transition: 'height 0.8s ease', | |
| minHeight: '4px' | |
| }"></div> | |
| <div style="font-size: 0.68rem; color: var(--text-tertiary); white-space: nowrap;"> | |
| {{ bar.label }} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| </div> | |
| <script> | |
| const { createApp, ref, computed, onMounted } = Vue | |
| createApp({ | |
| setup() { | |
| // Stat cards | |
| const stats = ref([ | |
| { | |
| icon: 'revenue', label: 'Total Revenue', value: '$48,295', | |
| change: '+12.5%', changeDir: 'up', color: 'amber', | |
| strokeColor: '#F0B429', | |
| sparkline: '0,28 15,24 30,26 45,18 60,20 75,12 90,14 105,8 120,4' | |
| }, | |
| { | |
| icon: 'users', label: 'Active Users', value: '2,847', | |
| change: '+8.2%', changeDir: 'up', color: 'teal', | |
| strokeColor: '#2DD4BF', | |
| sparkline: '0,24 15,22 30,18 45,20 60,14 75,16 90,10 105,12 120,6' | |
| }, | |
| { | |
| icon: 'orders', label: 'New Orders', value: '1,024', | |
| change: '-2.4%', changeDir: 'down', color: 'coral', | |
| strokeColor: '#FF6B6B', | |
| sparkline: '0,8 15,12 30,10 45,16 60,14 75,20 90,18 105,24 120,22' | |
| }, | |
| { | |
| icon: 'uptime', label: 'Uptime', value: '99.97%', | |
| change: '+0.02%', changeDir: 'up', color: 'violet', | |
| strokeColor: '#A78BFA', | |
| sparkline: '0,6 15,4 30,6 45,4 60,5 75,4 90,3 105,4 120,2' | |
| } | |
| ]) | |
| // Chart | |
| const chartTab = ref('1M') | |
| const chartW = 620 | |
| const chartH = 220 | |
| const chartPad = 45 | |
| const chartData = ref([32, 45, 38, 62, 55, 72, 68, 85, 78, 92, 88, 95]) | |
| const chartData2 = ref([20, 28, 25, 40, 35, 48, 42, 55, 50, 60, 56, 62]) | |
| const yLabels = ['$100K', '$75K', '$50K', '$25K', '$0'] | |
| const xLabels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | |
| const getPoint = (data, index, total) => { | |
| const x = chartPad + index * ((chartW - chartPad - 10) / (total - 1)) | |
| const y = chartPad + (1 - data / 100) * (chartH - chartPad * 2) | |
| return { x, y } | |
| } | |
| const chartPoints = computed(() => | |
| chartData.value.map((d, i) => getPoint(d, i, chartData.value.length)) | |
| ) | |
| const linePath = computed(() => | |
| chartPoints.value.map((p, i) => (i === 0 ? 'M' : 'L') + p.x + ',' + p.y).join(' ') | |
| ) | |
| const chartPoints2 = computed(() => | |
| chartData2.value.map((d, i) => getPoint(d, i, chartData2.value.length)) | |
| ) | |
| const linePath2 = computed(() => | |
| chartPoints2.value.map((p, i) => (i === 0 ? 'M' : 'L') + p.x + ',' + p.y).join(' ') | |
| ) | |
| const areaPath = computed(() => { | |
| const pts = chartPoints.value | |
| if (!pts.length) return '' | |
| const bottom = chartH - chartPad | |
| return `M${pts[0].x},${bottom} ` + | |
| pts.map(p => `L${p.x},${p.y}`).join(' ') + | |
| ` L${pts[pts.length-1].x},${bottom} Z` | |
| }) | |
| const areaPath2 = computed(() => { | |
| const pts = chartPoints2.value | |
| if (!pts.length) return '' | |
| const bottom = chartH - chartPad | |
| return `M${pts[0].x},${bottom} ` + | |
| pts.map(p => `L${p.x},${p.y}`).join(' ') + | |
| ` L${pts[pts.length-1].x},${bottom} Z` | |
| }) | |
| // System Stats | |
| const gauges = ref([ | |
| { label: 'CPU Usage', value: 67, detail: '4.2 GHz / 8 cores', color: '#F0B429' }, | |
| { label: 'Memory', value: 82, detail: '13.1 GB / 16 GB', color: '#FF6B6B' }, | |
| { label: 'Storage', value: 45, detail: '461 GB / 1 TB', color: '#2DD4BF' }, | |
| { label: 'Network', value: 34, detail: '340 Mbps / 1 Gbps', color: '#A78BFA' }, | |
| { label: 'GPU', value: 58, detail: '12.4 GB / 24 GB VRAM', color: '#F0B429' } | |
| ]) | |
| // Activity Feed | |
| const activities = ref([ | |
| { text: '<strong>Sarah Chen</strong> deployed <strong>v2.4.1</strong> to production', time: '2 min ago', color: 'amber' }, | |
| { text: '<strong>API Gateway</strong> latency spike detected (>200ms)', time: '15 min ago', color: 'coral' }, | |
| { text: '<strong>Marcus Wei</strong> merged PR #847 into main', time: '32 min ago', color: 'teal' }, | |
| { text: '<strong>Auto-scaler</strong> added 2 instances to us-east cluster', time: '1 hour ago', color: 'violet' }, | |
| { text: '<strong>CI Pipeline</strong> build #1204 passed all 342 tests', time: '1.5 hours ago', color: 'teal' }, | |
| { text: '<strong>Aisha Patel</strong> updated DNS records for staging', time: '3 hours ago', color: 'amber' } | |
| ]) | |
| // Tasks | |
| const tasks = ref([ | |
| { text: 'Review authentication flow PR', done: false, priority: 'high' }, | |
| { text: 'Update API rate limiting config', done: false, priority: 'high' }, | |
| { text: 'Migrate database to v3 schema', done: true, priority: 'medium' }, | |
| { text: 'Write integration test suite', done: false, priority: 'medium' }, | |
| { text: 'Optimize image CDN caching', done: true, priority: 'low' }, | |
| { text: 'Document webhook endpoints', done: false, priority: 'low' } | |
| ]) | |
| const completedTasks = computed(() => tasks.value.filter(t => t.done).length) | |
| // Weather | |
| const weather = ref({ | |
| icon: '\u2600\uFE0F', | |
| temp: '24\u00B0C', | |
| desc: 'Clear Sky', | |
| location: '40.7128\u00B0N, 74.0060\u00B0W', | |
| details: [ | |
| { value: '62%', label: 'Humidity' }, | |
| { value: '12 km/h', label: 'Wind' }, | |
| { value: '1013 hPa', label: 'Pressure' }, | |
| { value: '10 km', label: 'Visibility' } | |
| ] | |
| }) | |
| // Calendar | |
| const now = new Date() | |
| const calMonth = ref(now.getMonth()) | |
| const calYear = ref(now.getFullYear()) | |
| const monthNames = ['January','February','March','April','May','June','July','August','September','October','November','December'] | |
| const dows = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'] | |
| const monthName = computed(() => monthNames[calMonth.value]) | |
| const year = computed(() => calYear.value) | |
| const eventDays = [6, 12, 15, 22, 28] | |
| const calendarDays = computed(() => { | |
| const days = [] | |
| const firstDay = new Date(calYear.value, calMonth.value, 1) | |
| let startDow = firstDay.getDay() - 1 | |
| if (startDow < 0) startDow = 6 | |
| const prevMonthDays = new Date(calYear.value, calMonth.value, 0).getDate() | |
| for (let i = startDow - 1; i >= 0; i--) { | |
| days.push({ num: prevMonthDays - i, other: true, isToday: false, hasEvent: false }) | |
| } | |
| const daysInMonth = new Date(calYear.value, calMonth.value + 1, 0).getDate() | |
| for (let d = 1; d <= daysInMonth; d++) { | |
| const isToday = d === now.getDate() && calMonth.value === now.getMonth() && calYear.value === now.getFullYear() | |
| days.push({ num: d, other: false, isToday, hasEvent: eventDays.includes(d) }) | |
| } | |
| const remaining = 42 - days.length | |
| for (let d = 1; d <= remaining; d++) { | |
| days.push({ num: d, other: true, isToday: false, hasEvent: false }) | |
| } | |
| return days | |
| }) | |
| const prevMonth = () => { | |
| if (calMonth.value === 0) { calMonth.value = 11; calYear.value-- } | |
| else calMonth.value-- | |
| } | |
| const nextMonth = () => { | |
| if (calMonth.value === 11) { calMonth.value = 0; calYear.value++ } | |
| else calMonth.value++ | |
| } | |
| const events = ref([ | |
| { name: 'Sprint Planning', time: 'Today, 10:00 AM', color: 'amber' }, | |
| { name: 'Design Review', time: 'Today, 2:30 PM', color: 'teal' }, | |
| { name: 'Team Standup', time: 'Tomorrow, 9:00 AM', color: 'violet' } | |
| ]) | |
| // Traffic Bars | |
| const trafficBars = ref([ | |
| { label: 'Direct', value: '4.2K', height: 72, colorFrom: 'rgba(240,180,41,0.6)', colorTo: '#F0B429' }, | |
| { label: 'Organic', value: '3.8K', height: 65, colorFrom: 'rgba(45,212,191,0.6)', colorTo: '#2DD4BF' }, | |
| { label: 'Referral', value: '2.1K', height: 38, colorFrom: 'rgba(167,139,250,0.6)', colorTo: '#A78BFA' }, | |
| { label: 'Social', value: '1.9K', height: 34, colorFrom: 'rgba(255,107,107,0.6)', colorTo: '#FF6B6B' }, | |
| { label: 'Email', value: '1.4K', height: 26, colorFrom: 'rgba(240,180,41,0.6)', colorTo: '#F0B429' }, | |
| { label: 'Paid', value: '3.1K', height: 54, colorFrom: 'rgba(45,212,191,0.6)', colorTo: '#2DD4BF' }, | |
| { label: 'Display', value: '0.8K', height: 16, colorFrom: 'rgba(167,139,250,0.6)', colorTo: '#A78BFA' } | |
| ]) | |
| return { | |
| stats, chartTab, chartW, chartH, chartPad, | |
| yLabels, xLabels, chartPoints, linePath, linePath2, | |
| areaPath, areaPath2, | |
| gauges, activities, tasks, completedTasks, | |
| weather, calMonth, calYear, monthName, year, dows, | |
| calendarDays, prevMonth, nextMonth, events, | |
| trafficBars | |
| } | |
| } | |
| }).mount('#app') | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment