botserver/web/app/client-nav.html

341 lines
12 KiB
HTML
Raw Normal View History

<script type="module">
// 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>