botserver/web/app/client-nav.html

493 lines
13 KiB
HTML
Raw Normal View History

<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>