738 lines
No EOL
20 KiB
Svelte
738 lines
No EOL
20 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { page } from '$app/stores';
|
|
import { goto } from '$app/navigation';
|
|
import { discordDataStore, discordTokenStore, authTokenStore } from '$lib/stores';
|
|
import { slide } from 'svelte/transition';
|
|
|
|
let isAuthenticated = false;
|
|
let profileDropdownOpen = false;
|
|
let mobileMenuOpen = false;
|
|
let userProfile: { name: string; avatar?: string } | null = null;
|
|
|
|
onMount(() => {
|
|
// Check authentication on mount
|
|
isAuthenticated = !!$authTokenStore && !!$discordDataStore;
|
|
|
|
if (isAuthenticated) {
|
|
userProfile = {
|
|
name: $discordDataStore.global_name,
|
|
avatar: `https://cdn.discordapp.com/avatars/${$discordDataStore.id}/${$discordDataStore.avatar}.png`
|
|
};
|
|
}
|
|
});
|
|
|
|
function toggleProfileDropdown() {
|
|
profileDropdownOpen = !profileDropdownOpen;
|
|
}
|
|
|
|
function toggleMobileMenu() {
|
|
mobileMenuOpen = !mobileMenuOpen;
|
|
}
|
|
|
|
function logout() {
|
|
authTokenStore.set(null);
|
|
discordDataStore.set(null);
|
|
discordTokenStore.set(null);
|
|
isAuthenticated = false;
|
|
userProfile = null;
|
|
profileDropdownOpen = false;
|
|
mobileMenuOpen = false;
|
|
goto('/');
|
|
}
|
|
|
|
function handleClickOutside(event: MouseEvent) {
|
|
const dropdown = document.querySelector('.profile-container');
|
|
const mobileMenu = document.querySelector('.mobile-menu');
|
|
const hamburger = document.querySelector('.hamburger-button');
|
|
|
|
if (dropdown && !dropdown.contains(event.target as Node) && profileDropdownOpen) {
|
|
profileDropdownOpen = false;
|
|
}
|
|
|
|
if (mobileMenu && !mobileMenu.contains(event.target as Node) &&
|
|
hamburger && !hamburger.contains(event.target as Node) && mobileMenuOpen) {
|
|
mobileMenuOpen = false;
|
|
}
|
|
}
|
|
|
|
function handleNavClick() {
|
|
mobileMenuOpen = false;
|
|
}
|
|
</script>
|
|
|
|
<svelte:window on:click={handleClickOutside} />
|
|
|
|
<div class="layout dark-mode">
|
|
<header>
|
|
<div class="navbar">
|
|
<div class="navbar-section logo-section">
|
|
<img class="logo-svg" src="/assets/LogoTextC_DevW.svg" alt="">
|
|
</div>
|
|
|
|
<!-- Desktop Navigation -->
|
|
<nav class="navbar-section nav-links desktop-nav">
|
|
<a href="/" class:active={$page.url.pathname === '/'}>
|
|
<span>Home</span>
|
|
<div class="hover-indicator"></div>
|
|
</a>
|
|
<a href="/tournaments" class:active={$page.url.pathname === '/tournaments'}>
|
|
<span>View Tournaments</span>
|
|
<div class="hover-indicator"></div>
|
|
</a>
|
|
<a href="/authTokens" class:active={$page.url.pathname === '/authTokens'}>
|
|
<span>My Tokens</span>
|
|
<div class="hover-indicator"></div>
|
|
</a>
|
|
</nav>
|
|
|
|
<!-- Desktop Profile -->
|
|
<div class="navbar-section profile-container desktop-profile">
|
|
<button class="profile-button" on:click={toggleProfileDropdown}>
|
|
{#if isAuthenticated && userProfile?.avatar}
|
|
<img src={userProfile.avatar} alt="Profile" class="profile-image" />
|
|
{:else}
|
|
<span class="material-icons">account_circle</span>
|
|
<span class="login-text">{isAuthenticated ? userProfile?.name : 'Login'}</span>
|
|
{/if}
|
|
</button>
|
|
|
|
{#if profileDropdownOpen}
|
|
<div class="profile-dropdown" transition:slide={{duration: 200}}>
|
|
{#if isAuthenticated}
|
|
<a href="/authTokens" class="dropdown-item" on:click={handleNavClick}>
|
|
<span class="material-icons">vpn_key</span>
|
|
<span>Manage Auth (MAT)</span>
|
|
</a>
|
|
<div class="divider"></div>
|
|
<button class="dropdown-item logout" on:click={logout}>
|
|
<span class="material-icons">exit_to_app</span>
|
|
<span>Logout</span>
|
|
</button>
|
|
{:else}
|
|
<a href="/discordAuth" class="dropdown-item" on:click={handleNavClick}>
|
|
<span class="material-icons">login</span>
|
|
<span>Login</span>
|
|
</a>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Mobile Hamburger Menu -->
|
|
<button class="hamburger-button mobile-only" on:click={toggleMobileMenu}>
|
|
<span class="material-icons">{mobileMenuOpen ? 'close' : 'menu'}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Mobile Menu -->
|
|
{#if mobileMenuOpen}
|
|
<div class="mobile-menu" transition:slide={{duration: 300}}>
|
|
<nav class="mobile-nav-links">
|
|
<a href="/" class:active={$page.url.pathname === '/'} on:click={handleNavClick}>
|
|
<span class="material-icons">home</span>
|
|
<span>Home</span>
|
|
</a>
|
|
<a href="/tournaments" class:active={$page.url.pathname === '/tournaments'} on:click={handleNavClick}>
|
|
<span class="material-icons">sports_esports</span>
|
|
<span>View Tournaments</span>
|
|
</a>
|
|
<a href="/authTokens" class:active={$page.url.pathname === '/authTokens'} on:click={handleNavClick}>
|
|
<span class="material-icons">vpn_key</span>
|
|
<span>My Tokens</span>
|
|
</a>
|
|
</nav>
|
|
|
|
<div class="mobile-profile-section">
|
|
{#if isAuthenticated}
|
|
<div class="mobile-user-info">
|
|
{#if userProfile?.avatar}
|
|
<img src={userProfile.avatar} alt="Profile" class="mobile-profile-image" />
|
|
{:else}
|
|
<span class="material-icons">account_circle</span>
|
|
{/if}
|
|
<span class="mobile-user-name">{userProfile?.name}</span>
|
|
</div>
|
|
<div class="mobile-profile-divider"></div>
|
|
<button class="mobile-logout-button" on:click={logout}>
|
|
<span class="material-icons">exit_to_app</span>
|
|
<span>Logout</span>
|
|
</button>
|
|
{:else}
|
|
<a href="/discordAuth" class="mobile-login-button" on:click={handleNavClick}>
|
|
<span class="material-icons">login</span>
|
|
<span>Login</span>
|
|
</a>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</header>
|
|
|
|
<main>
|
|
<div class="content-container">
|
|
<slot />
|
|
</div>
|
|
</main>
|
|
|
|
<footer>
|
|
<div class="footer-content">
|
|
<div class="footer-section brand">
|
|
<h3>Tournament Assistant</h3>
|
|
<p>Helping you manage tournaments with ease</p>
|
|
</div>
|
|
|
|
<div class="footer-section links">
|
|
<h4>Links</h4>
|
|
<div class="footer-links">
|
|
<a href="https://github.com/ServerBP" target="_blank" rel="noopener noreferrer">
|
|
<span class="material-icons">code</span>
|
|
GitHub
|
|
</a>
|
|
<a href="https://tournamentassistant.net" target="_blank" rel="noopener noreferrer">
|
|
<span class="material-icons">sports_esports</span>
|
|
Tournament Assistant
|
|
</a>
|
|
<a href="/documentation" target="_blank" rel="noopener noreferrer">
|
|
<span class="material-icons">menu_book</span>
|
|
Documentation
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="footer-section contact">
|
|
<h4>Contact</h4>
|
|
<a href="mailto:support@beatkhana.com" class="contact-link">
|
|
<span class="material-icons">email</span>
|
|
support@beatkhana.com
|
|
</a>
|
|
<a href="https://discord.gg/AnkmKk6AD8" class="contact-link">
|
|
<span class="material-icons">forum</span>
|
|
Join our Discord
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="footer-bottom">
|
|
<div class="copyright">
|
|
© {new Date().getFullYear()} Luna & Moon. All rights reserved.
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
|
|
<style>
|
|
/* Dark mode variables */
|
|
:root {
|
|
--bg-primary: #121212;
|
|
--bg-secondary: #1e1e1e;
|
|
--bg-tertiary: #2d2d2d;
|
|
--text-primary: #ffffff;
|
|
--text-secondary: #b3b3b3;
|
|
--accent-color: #4f46e5;
|
|
--accent-hover: #6366f1;
|
|
--accent-glow: rgba(99, 102, 241, 0.5);
|
|
--danger-color: #ef4444;
|
|
--danger-hover: #dc2626;
|
|
--border-color: #333333;
|
|
--navbar-height: 4rem;
|
|
--footer-bg: #191919;
|
|
--success-color: #22c55e;
|
|
--warning-color: #f59e0b;
|
|
--error-color: #ef4444;
|
|
|
|
--comfortable-red: #d9534f;
|
|
--comfortable-red-hover: #c9302c;
|
|
--pick-green: #28a745;
|
|
--pick-green-hover: #218838;
|
|
}
|
|
|
|
/* Global styles */
|
|
.layout {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
background-color: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
}
|
|
|
|
/* Header and navbar */
|
|
header {
|
|
background-color: var(--bg-secondary);
|
|
border-bottom: 1px solid var(--border-color);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.navbar {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto 1fr;
|
|
align-items: center;
|
|
height: var(--navbar-height);
|
|
padding: 0 1.5rem;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
}
|
|
|
|
.navbar-section {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.logo-section {
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.logo-svg {
|
|
height: 40px;
|
|
width: auto;
|
|
max-width: none;
|
|
}
|
|
|
|
/* Desktop Navigation */
|
|
.desktop-nav {
|
|
justify-content: center;
|
|
gap: 2rem;
|
|
height: 100%;
|
|
}
|
|
|
|
.nav-links a {
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
padding: 0 0.75rem;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
position: relative;
|
|
transition: color 0.2s ease;
|
|
}
|
|
|
|
.nav-links a:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.nav-links a.active {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.nav-links a .hover-indicator {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 0;
|
|
height: 3px;
|
|
background: linear-gradient(90deg, transparent, var(--accent-color), transparent);
|
|
transition: width 0.3s ease;
|
|
opacity: 0;
|
|
}
|
|
|
|
.nav-links a:hover .hover-indicator {
|
|
width: 100%;
|
|
opacity: 1;
|
|
box-shadow: 0 0 8px var(--accent-glow);
|
|
}
|
|
|
|
.nav-links a.active .hover-indicator {
|
|
width: 100%;
|
|
opacity: 1;
|
|
box-shadow: 0 0 12px var(--accent-glow);
|
|
background: linear-gradient(90deg, transparent, var(--accent-hover), transparent);
|
|
}
|
|
|
|
/* Desktop Profile */
|
|
.desktop-profile {
|
|
position: relative;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.profile-button {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.5rem 0.75rem;
|
|
background-color: transparent;
|
|
border: none;
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
border-radius: 0.375rem;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
.profile-button:hover {
|
|
background-color: var(--bg-tertiary);
|
|
}
|
|
|
|
.profile-image {
|
|
width: 2.5rem;
|
|
height: 2.5rem;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
border: 2px solid var(--accent-color);
|
|
}
|
|
|
|
.material-icons {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.login-text {
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Profile dropdown */
|
|
.profile-dropdown {
|
|
position: absolute;
|
|
top: calc(100% + 0.5rem);
|
|
right: 0;
|
|
background-color: var(--bg-secondary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 0.75rem;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
width: 220px;
|
|
z-index: 10;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.dropdown-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 0.75rem 1rem;
|
|
color: var(--text-primary);
|
|
text-decoration: none;
|
|
transition: background-color 0.2s ease;
|
|
width: 100%;
|
|
text-align: left;
|
|
border: none;
|
|
background: none;
|
|
font-size: 0.875rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.dropdown-item:hover {
|
|
background-color: var(--bg-tertiary);
|
|
}
|
|
|
|
.dropdown-item.logout {
|
|
color: var(--danger-color);
|
|
}
|
|
|
|
.dropdown-item.logout:hover {
|
|
background-color: rgba(239, 68, 68, 0.1);
|
|
}
|
|
|
|
.divider {
|
|
height: 1px;
|
|
background-color: var(--border-color);
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
/* Mobile-specific styles */
|
|
.mobile-only {
|
|
display: none;
|
|
}
|
|
|
|
@media (min-width: 769px) {
|
|
.mobile-only {
|
|
display: none !important;
|
|
}
|
|
}
|
|
|
|
.hamburger-button {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
padding: 0.5rem;
|
|
border-radius: 0.375rem;
|
|
transition: background-color 0.2s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.hamburger-button:hover {
|
|
background-color: var(--bg-tertiary);
|
|
}
|
|
|
|
.mobile-menu {
|
|
background-color: var(--bg-secondary);
|
|
border-top: 1px solid var(--border-color);
|
|
padding: 1rem 0;
|
|
}
|
|
|
|
.mobile-nav-links {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 0 1.5rem;
|
|
}
|
|
|
|
.mobile-nav-links a {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
padding: 1rem 0;
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
transition: color 0.2s ease;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.mobile-nav-links a:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.mobile-nav-links a:hover,
|
|
.mobile-nav-links a.active {
|
|
color: var(--accent-color);
|
|
}
|
|
|
|
.mobile-nav-links .material-icons {
|
|
font-size: 1.25rem;
|
|
}
|
|
|
|
.mobile-profile-section {
|
|
margin-top: 1rem;
|
|
padding: 1rem 1.5rem 0;
|
|
border-top: 1px solid var(--border-color);
|
|
}
|
|
|
|
.mobile-user-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.mobile-profile-image {
|
|
width: 2rem;
|
|
height: 2rem;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
border: 2px solid var(--accent-color);
|
|
}
|
|
|
|
.mobile-user-name {
|
|
color: var(--text-primary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.mobile-profile-divider {
|
|
height: 1px;
|
|
background-color: var(--border-color);
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.mobile-logout-button,
|
|
.mobile-login-button {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
padding: 0.75rem 0;
|
|
background: none;
|
|
border: none;
|
|
color: var(--danger-color);
|
|
text-decoration: none;
|
|
font-size: 1rem;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
text-align: left;
|
|
transition: color 0.2s ease;
|
|
}
|
|
|
|
.mobile-login-button {
|
|
color: var(--accent-color);
|
|
}
|
|
|
|
.mobile-logout-button:hover {
|
|
color: var(--danger-hover);
|
|
}
|
|
|
|
.mobile-login-button:hover {
|
|
color: var(--accent-hover);
|
|
}
|
|
|
|
/* Main content */
|
|
main {
|
|
flex: 1;
|
|
padding: 2rem 1.5rem;
|
|
}
|
|
|
|
.content-container {
|
|
max-width: 1250px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
overflow-y: visible;
|
|
}
|
|
|
|
/* Footer */
|
|
footer {
|
|
background-color: var(--footer-bg);
|
|
border-top: 1px solid var(--border-color);
|
|
margin-top: auto;
|
|
padding-top: 2.5rem;
|
|
}
|
|
|
|
.footer-content {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 1fr;
|
|
gap: 2rem;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
padding: 0 1.5rem 2.5rem;
|
|
}
|
|
|
|
.footer-section h3 {
|
|
font-size: 1.25rem;
|
|
margin-bottom: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.footer-section h4 {
|
|
font-size: 1rem;
|
|
margin-bottom: 1rem;
|
|
color: var(--accent-color);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.footer-section p {
|
|
color: var(--text-secondary);
|
|
font-size: 0.875rem;
|
|
margin-bottom: 1rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.footer-links {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.footer-links a, .contact-link {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
transition: color 0.2s ease;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.footer-links a:hover, .contact-link:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.footer-links .material-icons, .contact-link .material-icons {
|
|
font-size: 1.125rem;
|
|
}
|
|
|
|
.contact-link {
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.footer-bottom {
|
|
background-color: rgba(0, 0, 0, 0.2);
|
|
padding: 1rem 0;
|
|
text-align: center;
|
|
}
|
|
|
|
.copyright {
|
|
color: var(--text-secondary);
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
/* Media queries */
|
|
@media (max-width: 768px) {
|
|
/* Hide desktop navigation and profile */
|
|
.desktop-nav,
|
|
.desktop-profile {
|
|
display: none;
|
|
}
|
|
|
|
/* Show mobile hamburger */
|
|
.mobile-only {
|
|
display: flex !important;
|
|
}
|
|
|
|
/* Adjust navbar layout for mobile */
|
|
.navbar {
|
|
grid-template-columns: auto 1fr auto;
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
.logo-section {
|
|
justify-content: flex-start;
|
|
overflow: hidden;
|
|
min-width: 0;
|
|
}
|
|
|
|
.logo-svg {
|
|
height: 32px;
|
|
max-width: calc(100vw - 8rem);
|
|
width: auto;
|
|
}
|
|
|
|
.hamburger-button {
|
|
flex-shrink: 0;
|
|
justify-self: end;
|
|
}
|
|
|
|
/* Additional breakpoint for very narrow screens like folded phones */
|
|
@media (max-width: 480px) {
|
|
.logo-svg {
|
|
height: 28px;
|
|
max-width: calc(100vw - 6rem);
|
|
}
|
|
|
|
.navbar {
|
|
padding: 0 0.75rem;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 360px) {
|
|
.logo-svg {
|
|
height: 24px;
|
|
max-width: calc(100vw - 5rem);
|
|
}
|
|
|
|
.navbar {
|
|
padding: 0 0.5rem;
|
|
}
|
|
}
|
|
|
|
/* Footer adjustments */
|
|
.footer-content {
|
|
grid-template-columns: 1fr;
|
|
text-align: center;
|
|
}
|
|
|
|
.footer-links {
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.contact-link {
|
|
justify-content: center;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 769px) and (max-width: 1024px) {
|
|
.navbar {
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
.footer-content {
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
|
|
.footer-section.brand {
|
|
grid-column: span 2;
|
|
text-align: center;
|
|
}
|
|
}
|
|
</style> |