botserver/web/app/client-nav.html

493 lines
No EOL
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<client-nav>
<script>
// Import icons from Lucide (using CDN)
import { HardDrive, Terminal, ChevronLeft, ChevronRight } from 'https://cdn.jsdelivr.net/npm/lucide@0.331.0/+esm';
export default {
// Component state
data() {
return {
examples: [
{ name: "Chat", href: "/chat", color: "#25D366" },
{ name: "Paper", href: "/paper", color: "#6366F1" },
{ name: "Mail", href: "/mail", color: "#FFD700" },
{ name: "Calendar", href: "/calendar", color: "#1DB954" },
{ name: "Meet", href: "/meet", color: "#059669" },
{ name: "Drive", href: "/drive", color: "#10B981" },
{ name: "Editor", href: "/editor", color: "#2563EB" },
{ name: "Player", href: "/player", color: "Yellow" },
{ name: "Tables", href: "/tables", color: "#8B5CF6" },
{ name: "Dashboard", href: "/dashboard", color: "#6366F1" },
{ name: "Sources", href: "/sources", color: "#F59E0B" },
{ name: "Settings", href: "/settings", color: "#6B7280" },
],
pathname: window.location.pathname,
scrollContainer: null,
navItems: [],
isLoggedIn: false,
showLoginMenu: false,
showScrollButtons: false,
loginMenu: null,
showThemeMenu: false,
themeMenu: null,
currentTime: new Date(),
theme: { name: "default", label: "Default", icon: "🎨" },
themes: [
{ name: "retrowave", label: "Retrowave", icon: "🌌" },
{ name: "vapordream", label: "Vapordream", icon: "🌀" },
{ name: "y2kglow", label: "Y2K Glow", icon: "💿" },
{ name: "mellowgold", label: "Mellow Gold", icon: "☮️" },
{ name: "arcadeflash", label: "Arcade Flash", icon: "🕹️" },
{ name: "polaroidmemories", label: "Polaroid Memories", icon: "📸" },
{ name: "midcenturymod", label: "MidCentury Mod", icon: "🪑" },
{ name: "grungeera", label: "Grunge Era", icon: "🎸" },
{ name: "discofever", label: "Disco Fever", icon: "🪩" },
{ name: "saturdaycartoons", label: "Saturday Cartoons", icon: "📺" },
{ name: "oldhollywood", label: "Old Hollywood", icon: "🎬" },
{ name: "cyberpunk", label: "Cyberpunk", icon: "🤖" },
{ name: "seasidepostcard", label: "Seaside Postcard", icon: "🏖️" },
{ name: "typewriter", label: "Typewriter", icon: "⌨️" },
{ name: "jazzage", label: "Jazz Age", icon: "🎷" },
{ name: "xtreegold", label: "XTree Gold", icon: "X" },
],
};
},
// Lifecycle: component mounted
mounted() {
// References
this.scrollContainer = this.root.querySelector('.nav-scroll');
this.loginMenu = this.root.querySelector('.login-menu');
this.themeMenu = this.root.querySelector('.theme-menu');
// Initialize nav item refs
this.navItems = Array.from(this.root.querySelectorAll('.nav-item'));
// Time update interval
this.timeInterval = setInterval(() => {
this.currentTime = new Date();
this.update();
}, 1000);
// Scroll button visibility
this.checkScrollNeeded();
// Resize listener
window.addEventListener('resize', this.checkScrollNeeded);
// Clickoutside handling
document.addEventListener('mousedown', this.handleClickOutside);
},
// Cleanup
unmounted() {
clearInterval(this.timeInterval);
window.removeEventListener('resize', this.checkScrollNeeded);
document.removeEventListener('mousedown', this.handleClickOutside);
},
// Methods
formatTime(date) {
return date.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
},
formatDate(date) {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
},
isActive(href) {
if (href === '/') return this.pathname === href;
return this.pathname.startsWith(href);
},
handleLogin() {
this.isLoggedIn = true;
this.showLoginMenu = false;
this.update();
},
handleLogout() {
this.isLoggedIn = false;
this.showLoginMenu = false;
this.update();
},
checkScrollNeeded() {
if (this.scrollContainer) {
const container = this.scrollContainer;
const isScrollable = container.scrollWidth > container.clientWidth;
this.showScrollButtons = isScrollable;
this.update();
}
},
handleClickOutside(event) {
if (this.loginMenu && !this.loginMenu.contains(event.target)) {
this.showLoginMenu = false;
}
if (this.themeMenu && !this.themeMenu.contains(event.target)) {
this.showThemeMenu = false;
}
this.update();
},
scrollLeft() {
if (this.scrollContainer) {
this.scrollContainer.scrollBy({ left: -150, behavior: 'smooth' });
}
},
scrollRight() {
if (this.scrollContainer) {
this.scrollContainer.scrollBy({ left: 150, behavior: 'smooth' });
}
},
getThemeIcon() {
const found = this.themes.find(t => t.name === this.theme.name);
return found ? found.icon : '🎨';
},
setTheme(name) {
const found = this.themes.find(t => t.name === name);
if (found) {
this.theme = found;
this.showThemeMenu = false;
this.update();
}
},
navigate(href) {
window.location.href = href;
},
};
</script>
<style>
/* Basic styles - the original Tailwind classes are kept as comments for reference */
.fixed {
position: fixed;
}
.top-0 {
top: 0;
}
.left-0 {
left: 0;
}
.right-0 {
right: 0;
}
.z-50 {
z-index: 50;
}
.bg-gray-800 {
background-color: #2d3748;
}
.text-green-400 {
color: #68d391;
}
.font-mono {
font-family: monospace;
}
.border-b {
border-bottom: 1px solid transparent;
}
.border-green-600 {
border-color: #38a169;
}
.text-xs {
font-size: .75rem;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-1 {
padding-top: .25rem;
padding-bottom: .25rem;
}
.gap-4>*+* {
margin-left: 1rem;
}
.gap-2>*+* {
margin-left: .5rem;
}
.w-3 {
width: .75rem;
}
.h-3 {
height: .75rem;
}
.text-green-300 {
color: #9ae6b4;
}
.w-2 {
width: .5rem;
}
.h-2 {
height: .5rem;
}
.bg-green-500 {
background-color: #48bb78;
}
.rounded-full {
border-radius: 9999px;
}
.animate-pulse {
animation: pulse 2s infinite;
}
.nav-container {
position: relative;
}
.nav-inner {
overflow: hidden;
}
.nav-content {
display: flex;
align-items: center;
gap: .5rem;
}
.logo-container img {
display: block;
}
.nav-scroll {
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.nav-scroll::-webkit-scrollbar {
display: none;
}
.nav-items {
display: flex;
gap: .25rem;
}
.nav-item {
padding: .25rem .5rem;
border: 1px solid transparent;
border-radius: .25rem;
cursor: pointer;
transition: all .2s;
}
.nav-item.active {
background-color: #2d3748;
border-color: #68d391;
color: #68d391;
}
.nav-item:hover {
border-color: currentColor;
}
.auth-controls {
display: flex;
gap: .5rem;
}
.login-button,
.theme-toggle {
background: none;
border: none;
color: inherit;
cursor: pointer;
}
.login-menu,
.theme-menu {
position: absolute;
background: #1a202c;
border: 1px solid #4a5568;
padding: .5rem;
margin-top: .25rem;
border-radius: .25rem;
}
.menu-item {
display: block;
width: 100%;
text-align: left;
padding: .25rem;
background: none;
border: none;
color: #a0aec0;
cursor: pointer;
}
.menu-item:hover {
background: #2d3748;
}
.active-theme {
font-weight: bold;
}
</style>
<!-- Markup -->
<div class="fixed top-0 left-0 right-0 z-50 bg-gray-800 text-green-400 font-mono border-b border-green-600 text-xs">
<div class="flex items-center justify-between px-4 py-1">
<div class="flex items-center gap-4">
<div class="flex items-center gap-2">
<HardDrive class="w-3 h-3 text-green-400" />
<span class="text-green-300">RETRO NAVIGATOR v4.0</span>
</div>
<div class="flex items-center gap-1">
<div class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
<span class="text-green-400">READY</span>
</div>
<div class="flex items-center gap-1">
<span class="text-green-300">THEME:</span>
<span class="text-yellow-400">{theme.label}</span>
</div>
</div>
<div class="flex items-center gap-4">
<span class="text-green-300">{formatDate(currentTime)}</span>
<span class="text-green-300">{formatTime(currentTime)}</span>
<div class="flex items-center gap-1">
<Terminal class="w-3 h-3 text-green-400" />
<span class="text-green-400">SYS</span>
</div>
</div>
</div>
</div>
<div class="nav-container" style="top:24px;">
<div class="nav-inner">
<div class="nav-content">
<div class="logo-container">
<img src="/images/generalbots-logo.svg" alt="Logo" width="64" height="24" />
</div>
{#if showScrollButtons}
<button
class="w-8 h-8 bg-gray-800 border border-green-600 text-green-400 rounded hover:bg-green-900/30 hover:border-green-500 hover:text-green-300 transition-all flex items-center justify-center flex-shrink-0 mx-1"
@click="scrollLeft" aria-label="Scroll left">
<ChevronLeft class="w-4 h-4" />
</button>
{/if}
<div class="nav-scroll">
<div class="nav-items">
{#each examples as example, index}
<button class="nav-item {isActive(example.href) ? 'active' : ''}" @click="{() => navigate(example.href)}"
style="--neon-color:{example.color}" @mouseenter="{(e) => {
e.target.style.boxShadow = `0 0 15px ${example.color}60`;
e.target.style.borderColor = example.color;
e.target.style.color = example.color;
e.target.style.textShadow = `0 0 8px ${example.color}80`;
}}" @mouseleave="{(e) => {
if (!isActive(example.href)) {
e.target.style.boxShadow = 'none';
e.target.style.borderColor = '';
e.target.style.color = '';
e.target.style.textShadow = 'none';
}
}}">
{example.name}
<div class="neon-glow"></div>
</button>
{/each}
</div>
</div>
{#if showScrollButtons}
<button
class="w-8 h-8 bg-gray-800 border border-green-600 text-green-400 rounded hover:bg-green-900/30 hover:border-green-500 hover:text-green-300 transition-all flex items-center justify-center flex-shrink-0 mx-1"
@click="scrollRight" aria-label="Scroll right">
<ChevronRight class="w-4 h-4" />
</button>
{/if}
<div class="auth-controls">
<div class="login-container" bind:this="{loginMenu}">
<button @click="{() => showLoginMenu = !showLoginMenu}" class="login-button"
aria-label="{isLoggedIn ? 'User menu' : 'Login'}">
{isLoggedIn ? '👤' : '🔐'}
</button>
{#if showLoginMenu}
<div class="login-menu">
{#if !isLoggedIn}
<button @click="handleLogin" class="menu-item">Login</button>
{:else}
<button @click="handleLogout" class="menu-item">Logout</button>
{/if}
</div>
{/if}
</div>
<div class="theme-container" bind:this="{themeMenu}">
<button @click="{() => showThemeMenu = !showThemeMenu}" class="theme-toggle" aria-label="Change theme">
{getThemeIcon()}
</button>
{#if showThemeMenu}
<div class="theme-menu">
{#each themes as t}
<button @click="{() => setTheme(t.name)}"
class="theme-menu-item {theme.name === t.name ? 'active-theme' : ''}">
{t.label}
</button>
{/each}
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="nav-spacer" style="height:88px;"></div>
</div>
</client-nav>