| <style> | |
| @keyframes sweepIn { | |
| 0% { | |
| clip-path: polygon(0 0, 0% 0, 0% 100%, 0 100%); | |
| } | |
| 100% { | |
| clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); | |
| } | |
| } | |
| @keyframes sweepOut { | |
| 0% { | |
| clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); | |
| } | |
| 100% { | |
| clip-path: polygon(0 0, 0% 0, 0% 100%, 0 100%); | |
| } | |
| } | |
| @keyframes linkFadeIn { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background-color: #121212; | |
| margin: 0; | |
| color: #e0e0e0; | |
| } | |
| header { | |
| background: #1a1a1a; | |
| color: #e0e0e0; | |
| padding: 0.8rem 1.25rem; | |
| display: flex; | |
| align-items: center; | |
| z-index: 1001; | |
| position: relative; | |
| border-bottom: 1px solid #282828; | |
| } | |
| .menu-btn-wrapper { | |
| background: transparent; | |
| border: none; | |
| color: #00bcd4; | |
| cursor: pointer; | |
| padding: 0.5rem; | |
| margin-right: 1.2rem; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| width: 40px; | |
| height: 40px; | |
| z-index: 1003; | |
| position: relative; | |
| transition: transform 0.3s ease; | |
| } | |
| .menu-btn-wrapper:hover { | |
| transform: rotate(15deg); | |
| } | |
| .menu-btn-bar { | |
| display: block; | |
| width: 24px; | |
| height: 2.5px; | |
| background-color: #00bcd4; | |
| border-radius: 1px; | |
| transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); | |
| margin: 2.5px 0; | |
| } | |
| .menu-btn-wrapper.open .menu-btn-bar:nth-child(1) { | |
| transform: rotate(45deg) translate(4.5px, 4.5px) scaleX(1.2); | |
| } | |
| .menu-btn-wrapper.open .menu-btn-bar:nth-child(2) { | |
| opacity: 0; | |
| transform: scaleX(0); | |
| } | |
| .menu-btn-wrapper.open .menu-btn-bar:nth-child(3) { | |
| transform: rotate(-45deg) translate(4.5px, -4.5px) scaleX(1.2); | |
| } | |
| nav { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 270px; | |
| height: 100vh; | |
| background-color: #1e1e1e; | |
| z-index: 1002; | |
| box-shadow: 4px 0 15px rgba(0, 0, 0, 0.3); | |
| display: flex; | |
| flex-direction: column; | |
| clip-path: polygon(0 0, 0% 0, 0% 100%, 0 100%); | |
| visibility: hidden; | |
| } | |
| nav.open { | |
| animation: sweepIn 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; | |
| visibility: visible; | |
| transition: visibility 0s; | |
| } | |
| nav.closing { | |
| animation: sweepOut 0.4s cubic-bezier(0.55, 0.055, 0.675, 0.19) forwards; | |
| visibility: visible; | |
| } | |
| .sidebar-backdrop { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100vw; | |
| height: 100vh; | |
| background: rgba(0, 0, 0, 0.7); | |
| z-index: 1000; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 0.4s ease; | |
| } | |
| .sidebar-backdrop.open { | |
| opacity: 1; | |
| pointer-events: auto; | |
| } | |
| .nav-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 0.8rem 1rem 0.8rem 1.5rem; | |
| background-color: #161616; | |
| border-bottom: 1px solid #2a2a2a; | |
| } | |
| .nav-header-title { | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| color: #00bcd4; | |
| letter-spacing: 0.5px; | |
| } | |
| .close-btn-nav { | |
| background: transparent; | |
| border: none; | |
| color: #888; | |
| font-size: 1.6rem; | |
| font-weight: bold; | |
| cursor: pointer; | |
| padding: 0.5rem; | |
| transition: | |
| color 0.2s ease-in-out, | |
| transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); | |
| } | |
| .close-btn-nav:hover { | |
| color: #00bcd4; | |
| transform: rotate(180deg) scale(1.1); | |
| } | |
| .nav-links { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| padding: 0.75rem 0; | |
| overflow-y: auto; | |
| } | |
| .nav-links a { | |
| color: #b0b0b0; | |
| text-decoration: none; | |
| padding: 0.95rem 1.75rem; | |
| display: flex; | |
| align-items: center; | |
| position: relative; | |
| font-size: 0.95rem; | |
| letter-spacing: 0.3px; | |
| transition: | |
| color 0.25s ease, | |
| background-color 0.25s ease; | |
| opacity: 0; | |
| } | |
| nav.open .nav-links a { | |
| animation: linkFadeIn 0.3s ease-out forwards; | |
| } | |
| nav.open .nav-links a:nth-child(1) { | |
| animation-delay: 0.15s; | |
| } | |
| nav.open .nav-links a:nth-child(2) { | |
| animation-delay: 0.2s; | |
| } | |
| nav.open .nav-links a:nth-child(3) { | |
| animation-delay: 0.25s; | |
| } | |
| nav.open .nav-links a:nth-child(4) { | |
| animation-delay: 0.3s; | |
| } | |
| nav.open .nav-links a:nth-child(5) { | |
| animation-delay: 0.35s; | |
| } | |
| nav.open .nav-links a:nth-child(6) { | |
| animation-delay: 0.4s; | |
| } | |
| .nav-links a::before { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| bottom: 0; | |
| width: 0px; | |
| background-color: #00bcd4; | |
| transition: width 0.3s cubic-bezier(0.23, 1, 0.32, 1); | |
| } | |
| .nav-links a:hover { | |
| color: #ffffff; | |
| background-color: rgba(0, 188, 212, 0.1); | |
| } | |
| .nav-links a:hover::before, | |
| .nav-links a.active::before { | |
| width: 4px; | |
| } | |
| .nav-links a.active { | |
| color: #ffffff; | |
| font-weight: 500; | |
| background-color: rgba(0, 188, 212, 0.15); | |
| } | |
| .nav-links a.active::before { | |
| width: 4px; | |
| } | |
| h1.header-title { | |
| margin: 0; | |
| flex-grow: 1; | |
| font-weight: 700; | |
| font-size: 1.5rem; | |
| color: #f0f0f0; | |
| letter-spacing: 0.5px; | |
| text-shadow: 0 0 5px rgba(0, 188, 212, 0.3); | |
| } | |
| </style> | |
| <header> | |
| <button class="menu-btn-wrapper" id="menuBtnWrapper" aria-label="Open menu" aria-expanded="false"> | |
| <span class="menu-btn-bar"></span> | |
| <span class="menu-btn-bar"></span> | |
| <span class="menu-btn-bar"></span> | |
| </button> | |
| <h1 class="header-title">Exocore Dashboard</h1> | |
| </header> | |
| <div class="sidebar-backdrop" id="sidebarBackdrop"></div> | |
| <nav id="sideNav"> | |
| <div class="nav-header"> | |
| <span class="nav-header-title">EXOCORE</span> | |
| <button class="close-btn-nav" id="closeBtnNav" aria-label="Close menu">×</button> | |
| </div> | |
| <div class="nav-links"> | |
| <a href="/private/server/exocore/web/public/dashboard">Dashboard</a> | |
| <a href="/private/server/exocore/web/public/profile">Profile</a> | |
| <a href="/private/server/exocore/web/public/manager">File Manager</a> | |
| <a href="/private/server/exocore/web/public/console">Console</a> | |
| <a href="/private/server/exocore/web/public/shell">Shell</a> | |
| <a href="#" id="logoutLink">Logout</a> | |
| </div> | |
| </nav> | |
| <script> | |
| const menuBtnWrapper = document.getElementById('menuBtnWrapper'); | |
| const sideNav = document.getElementById('sideNav'); | |
| const closeBtnNav = document.getElementById('closeBtnNav'); | |
| const backdrop = document.getElementById('sidebarBackdrop'); | |
| const navLinks = document.querySelectorAll('.nav-links a'); | |
| const logoutLink = document.getElementById('logoutLink'); | |
| let isNavAnimating = false; | |
| function openSidebar() { | |
| if (isNavAnimating || sideNav.classList.contains('open')) return; | |
| isNavAnimating = true; | |
| sideNav.classList.remove('closing'); | |
| sideNav.classList.add('open'); | |
| backdrop.classList.add('open'); | |
| menuBtnWrapper.classList.add('open'); | |
| menuBtnWrapper.setAttribute('aria-expanded', 'true'); | |
| sideNav.addEventListener( | |
| 'animationend', | |
| () => { | |
| isNavAnimating = false; | |
| }, | |
| { once: true } | |
| ); | |
| } | |
| function closeSidebar() { | |
| if (isNavAnimating || !sideNav.classList.contains('open')) return; | |
| isNavAnimating = true; | |
| sideNav.classList.remove('open'); | |
| sideNav.classList.add('closing'); | |
| backdrop.classList.remove('open'); | |
| menuBtnWrapper.classList.remove('open'); | |
| menuBtnWrapper.setAttribute('aria-expanded', 'false'); | |
| sideNav.addEventListener( | |
| 'animationend', | |
| () => { | |
| sideNav.classList.remove('closing'); | |
| isNavAnimating = false; | |
| }, | |
| { once: true } | |
| ); | |
| } | |
| function toggleSidebar() { | |
| if (sideNav.classList.contains('open')) { | |
| closeSidebar(); | |
| } else { | |
| openSidebar(); | |
| } | |
| } | |
| menuBtnWrapper.addEventListener('click', toggleSidebar); | |
| closeBtnNav.addEventListener('click', closeSidebar); | |
| backdrop.addEventListener('click', closeSidebar); | |
| const currentPath = window.location.pathname; | |
| const currentPathSpan = document.getElementById('currentPathSpan'); | |
| if (currentPathSpan) { | |
| currentPathSpan.textContent = currentPath; | |
| } | |
| navLinks.forEach((link) => { | |
| if (link.id !== 'logoutLink' && link.getAttribute('href') === currentPath) { | |
| link.classList.add('active'); | |
| } | |
| }); | |
| if (logoutLink) { | |
| logoutLink.addEventListener('click', async function(event) { | |
| event.preventDefault(); | |
| try { | |
| await fetch('/private/server/exocore/web/logout', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| }); | |
| } catch (error) { | |
| console.error('Logout fetch error:', error); | |
| } finally { | |
| localStorage.removeItem('exocore-token'); | |
| localStorage.removeItem('exocore-cookies'); | |
| window.location.href = '/private/server/exocore/web/public/login'; | |
| } | |
| }); | |
| } | |
| </script> | |