feat: Add mobile chat interface and command input to footer component
Some checks failed
GBCI / build (push) Failing after 8m35s

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-06-29 21:33:12 -03:00
parent 0908822924
commit a3c37e31ac
5 changed files with 575 additions and 235 deletions

View file

@ -4,6 +4,7 @@ import { usePathname, useRouter } from 'next/navigation';
import Image from 'next/image';
import { useRef, useEffect, useState } from 'react';
import { useTheme } from './theme-provider';
import { ChevronLeft, ChevronRight, HardDrive, Terminal } from 'lucide-react';
import './client-nav.css'; // Ensure you have the CSS file for styles
const examples = [
@ -21,7 +22,7 @@ const examples = [
{ name: "Settings", href: "/settings", color: "#6B7280" }, // Gray
];
export function Nav() {
export const Nav = ()=> {
const pathname = usePathname();
const router = useRouter();
const scrollContainerRef = useRef<HTMLDivElement>(null);
@ -32,8 +33,35 @@ export function Nav() {
const loginMenuRef = useRef<HTMLDivElement>(null);
const [showThemeMenu, setShowThemeMenu] = useState(false);
const themeMenuRef = useRef<HTMLDivElement>(null);
const [currentTime, setCurrentTime] = useState(new Date());
const { theme, setTheme, themes } = useTheme();
// Update time every second
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
const formatTime = (date) => {
return date.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};
const formatDate = (date) => {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
};
const isActive = (href: string) => {
if (href === '/') return pathname === href;
return pathname.startsWith(href);
@ -153,7 +181,36 @@ export function Nav() {
return (
<>
<div className="nav-container">
{/* Retro System Status Bar */}
<div className="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 className="flex items-center justify-between px-4 py-1">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<HardDrive className="w-3 h-3 text-green-400" />
<span className="text-green-300">RETRO NAVIGATOR v4.0</span>
</div>
<div className="flex items-center gap-1">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
<span className="text-green-400">READY</span>
</div>
<div className="flex items-center gap-1">
<span className="text-green-300">THEME:</span>
<span className="text-yellow-400">{theme.label}</span>
</div>
</div>
<div className="flex items-center gap-4">
<span className="text-green-300">{formatDate(currentTime)}</span>
<span className="text-green-300">{formatTime(currentTime)}</span>
<div className="flex items-center gap-1">
<Terminal className="w-3 h-3 text-green-400" />
<span className="text-green-400">SYS</span>
</div>
</div>
</div>
</div>
{/* Main Navigation */}
<div className="nav-container" style={{ top: '24px' }}>
<div className="nav-inner">
<div className="nav-content">
<div className="logo-container">
@ -166,8 +223,12 @@ export function Nav() {
</div>
{showScrollButtons && (
<button className="scroll-btn scroll-left" onClick={scrollLeft} aria-label="Scroll left">
&#8249;
<button
className="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"
onClick={scrollLeft}
aria-label="Scroll left"
>
<ChevronLeft className="w-4 h-4" />
</button>
)}
@ -182,6 +243,20 @@ export function Nav() {
onClick={() => router.push(example.href)}
className={`nav-item ${active ? 'active' : ''}`}
style={{'--neon-color': example.color} as React.CSSProperties}
onMouseEnter={(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`;
}}
onMouseLeave={(e) => {
if (!active) {
e.target.style.boxShadow = 'none';
e.target.style.borderColor = '';
e.target.style.color = '';
e.target.style.textShadow = 'none';
}
}}
>
{example.name}
<div className="neon-glow"></div>
@ -192,8 +267,12 @@ export function Nav() {
</div>
{showScrollButtons && (
<button className="scroll-btn scroll-right" onClick={scrollRight} aria-label="Scroll right">
&#8250;
<button
className="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"
onClick={scrollRight}
aria-label="Scroll right"
>
<ChevronRight className="w-4 h-4" />
</button>
)}
@ -255,8 +334,13 @@ export function Nav() {
</div>
</div>
<div className="nav-spacer"></div>
<div className="nav-spacer" style={{ height: '88px' }}></div>
</>
);
}
export default Nav;

View file

@ -1,7 +1,7 @@
"use client";
import React, { useState, useEffect } from 'react';
import { MessageCircle, Send, X, Minimize2, Maximize2, Monitor, Terminal, Cpu, HardDrive } from 'lucide-react';
import { MessageCircle, Send, X, Minimize2, Maximize2, Monitor, Terminal, Cpu, HardDrive, ChevronUp, ChevronDown } from 'lucide-react';
import {
ResizableHandle,
ResizablePanel,
@ -13,6 +13,8 @@ const Footer = ({ className = "", shortcuts }) => {
const [inputValue, setInputValue] = useState('');
const [isMobile, setIsMobile] = useState(false);
const [currentTime, setCurrentTime] = useState(new Date());
const [isMaximized, setIsMaximized] = useState(false);
const [isMinimized, setIsMinimized] = useState(false);
useEffect(() => {
const checkMobile = () => {
@ -89,6 +91,20 @@ const Footer = ({ className = "", shortcuts }) => {
}
};
const toggleMaximize = () => {
if (isMinimized) {
setIsMinimized(false);
}
setIsMaximized(!isMaximized);
};
const toggleMinimize = () => {
setIsMinimized(!isMinimized);
if (isMaximized) {
setIsMaximized(false);
}
};
const formatTime = (date) => {
return date.toLocaleTimeString('en-US', {
hour12: false,
@ -108,247 +124,348 @@ const Footer = ({ className = "", shortcuts }) => {
});
};
const getContainerHeight = () => {
if (isMinimized) return 'h-4';
if (isMaximized) return 'h-screen fixed inset-0 z-50 bg-background';
return isMobile ? 'h-64' : 'h-32';
};
const scrollbarStyles = `
/* Webkit browsers */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(128, 128, 128, 0.3);
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(128, 128, 128, 0.5);
}
/* Firefox */
* {
scrollbar-width: thin;
scrollbar-color: rgba(128, 128, 128, 0.3) rgba(0, 0, 0, 0.1);
}
`;
if (isMobile) {
return (
<div className={`h-80 bg-background border-t border-border ${className}`}>
<ResizablePanelGroup direction="vertical" className="h-full">
<ResizablePanel defaultSize={40} minSize={30}>
<div className="h-full flex flex-col bg-card border-b border-border">
<div className="flex items-center justify-between p-2 bg-muted border-b border-border">
<div className="flex items-center gap-2">
<Terminal className="w-4 h-4 text-primary" />
<span className="text-sm font-mono text-foreground">MOBILE TERMINAL</span>
</div>
<div className="text-xs text-muted-foreground font-mono">
{formatTime(currentTime)}
</div>
<>
<style dangerouslySetInnerHTML={{ __html: scrollbarStyles }} />
<div className={`${getContainerHeight()} bg-gradient-to-br from-background via-background to-muted/20 border-t border-border shadow-2xl ${className}`}>
{isMinimized ? (
<div className="h-full flex items-center justify-between px-2 bg-gradient-to-r from-muted/50 to-secondary/50 border-b border-border backdrop-blur-sm cursor-pointer" onClick={toggleMinimize}>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-gradient-to-br from-green-400 to-emerald-500 rounded-full animate-pulse shadow-lg" />
<span className="text-xs font-mono text-foreground font-medium">TERMINAL</span>
</div>
<div className="flex-1 p-3">
<div className="flex items-center gap-2 bg-background border border-input rounded-md">
<span className="text-primary text-sm font-mono pl-3">></span>
<input
placeholder="Enter command..."
className="flex-1 bg-transparent text-foreground placeholder:text-muted-foreground border-0 outline-none py-2 pr-3 text-sm font-mono"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
/>
<button
onClick={handleSendMessage}
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-r-md px-3 py-2 text-sm font-mono transition-colors flex items-center gap-1"
>
<Send className="w-3 h-3" />
</button>
</div>
<div className="text-xs text-muted-foreground font-mono bg-secondary/50 px-1.5 py-0.5 rounded-full">
{formatTime(currentTime)}
</div>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize={60} minSize={40}>
<div className="h-full flex flex-col bg-card">
<div className="flex items-center justify-between p-2 bg-muted border-b border-border">
<div className="flex items-center gap-2">
<Monitor className="w-4 h-4 text-primary" />
<span className="text-sm font-mono text-foreground">OUTPUT</span>
</div>
<div className="flex items-center gap-1">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
<span className="text-xs text-muted-foreground font-mono">READY</span>
</div>
</div>
<div className="flex-1 p-3 overflow-y-auto bg-background font-mono text-sm">
{messages.length === 0 ? (
<div className="text-muted-foreground text-center py-8">
<div className="animate-pulse text-primary"> SYSTEM READY </div>
<div className="mt-2 text-xs">Awaiting input...</div>
</div>
) : (
messages.map((message, index) => (
<div key={index} className="mb-3">
<div className="text-xs text-muted-foreground mb-1">
[{formatTime(message.timestamp)}] {message.sender.toUpperCase()}:
</div>
<div className={`p-2 rounded border ${
message.sender === 'user'
? 'bg-primary/10 border-primary text-foreground'
: message.sender === 'system'
? 'bg-accent/50 border-accent text-accent-foreground'
: 'bg-secondary/50 border-secondary text-secondary-foreground'
}`}>
{message.text}
) : (
<ResizablePanelGroup direction="vertical" className="h-full">
<ResizablePanel defaultSize={40} minSize={30}>
<div className="h-full flex flex-col bg-gradient-to-br from-card via-card to-secondary/20 border-b border-border/50 shadow-inner">
<div className="flex items-center justify-between p-1.5 bg-gradient-to-r from-muted/80 to-secondary/80 border-b border-border/50 backdrop-blur-sm cursor-pointer" onClick={toggleMaximize}>
<div className="flex items-center gap-2">
<div className="p-1 bg-primary/10 rounded border border-primary/20">
<Terminal className="w-3 h-3 text-primary" />
</div>
<span className="text-xs font-mono text-foreground font-medium">MOBILE TERMINAL</span>
</div>
))
)}
</div>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</div>
<div className="flex items-center gap-2">
<div className="text-xs text-muted-foreground font-mono bg-background/50 px-1.5 py-0.5 rounded-full border border-border/50">
{formatTime(currentTime)}
</div>
<div className="w-2 h-2 bg-gradient-to-br from-green-400 to-emerald-500 rounded-full animate-pulse shadow-lg" />
</div>
</div>
<div className="flex-1 p-2">
<div className="flex items-center gap-1 bg-gradient-to-r from-background to-background/80 border border-input/50 rounded shadow-inner backdrop-blur-sm">
<span className="text-primary text-xs font-mono pl-2 font-bold">></span>
<input
placeholder="Enter command..."
className="flex-1 bg-transparent text-foreground placeholder:text-muted-foreground border-0 outline-none py-2 pr-2 text-xs font-mono"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
/>
<button
onClick={handleSendMessage}
className="bg-gradient-to-r from-primary to-primary/90 text-primary-foreground hover:from-primary/90 hover:to-primary/80 rounded-r px-3 py-2 text-xs font-mono transition-all duration-200 flex items-center gap-1 shadow-lg hover:shadow-xl transform hover:scale-105"
>
<Send className="w-3 h-3" />
</button>
</div>
</div>
</div>
</ResizablePanel>
<ResizableHandle className="bg-gradient-to-r from-border to-primary/20 hover:from-primary/30 hover:to-primary/40 transition-all duration-200" />
<ResizablePanel defaultSize={60} minSize={40}>
<div className="h-full flex flex-col bg-gradient-to-br from-card via-card to-accent/10">
<div className="flex items-center justify-between p-1.5 bg-gradient-to-r from-muted/80 to-secondary/80 border-b border-border/50 backdrop-blur-sm cursor-pointer" onClick={toggleMinimize}>
<div className="flex items-center gap-2">
<div className="p-1 bg-primary/10 rounded border border-primary/20">
<Monitor className="w-3 h-3 text-primary" />
</div>
<span className="text-xs font-mono text-foreground font-medium">OUTPUT</span>
</div>
<div className="flex items-center gap-1">
<div className="w-2 h-2 bg-gradient-to-br from-green-400 to-emerald-500 rounded-full animate-pulse shadow-lg" />
<span className="text-xs text-muted-foreground font-mono bg-background/50 px-1.5 py-0.5 rounded-full border border-border/50">READY</span>
</div>
</div>
<div className="flex-1 p-2 overflow-y-auto bg-gradient-to-br from-background/50 to-secondary/20 font-mono text-xs backdrop-blur-sm">
{messages.length === 0 ? (
<div className="text-muted-foreground text-center py-8">
<div className="animate-pulse text-primary text-sm font-bold mb-1"> SYSTEM READY </div>
<div className="mt-1 text-xs bg-secondary/30 px-2 py-0.5 rounded-full inline-block">Awaiting input...</div>
</div>
) : (
messages.map((message, index) => (
<div key={index} className="mb-2 group">
<div className="text-xs text-muted-foreground mb-1 bg-secondary/20 px-1.5 py-0.5 rounded-t">
[{formatTime(message.timestamp)}] {message.sender.toUpperCase()}:
</div>
<div className={`p-2 rounded-b rounded-tr shadow-sm transition-all duration-200 group-hover:shadow-md border ${message.sender === 'user'
? 'bg-gradient-to-r from-primary/10 to-primary/5 border-primary/30 text-foreground'
: message.sender === 'system'
? 'bg-gradient-to-r from-accent/50 to-accent/30 border-accent/50 text-accent-foreground'
: 'bg-gradient-to-r from-secondary/50 to-secondary/30 border-secondary/50 text-secondary-foreground'
}`}>
{message.text}
</div>
</div>
))
)}
</div>
</div>
</ResizablePanel>
</ResizablePanelGroup>
)}
</div>
</>
);
}
return (
<div className={`h-48 bg-background border-t border-border ${className}`}>
<ResizablePanelGroup direction="horizontal" className="h-full">
<ResizablePanel defaultSize={30} minSize={20} maxSize={50}>
<div className="h-full flex flex-col bg-card">
<div className="flex items-center justify-between px-3 py-1 bg-muted border-b border-border">
<div className="flex items-center gap-2">
<Cpu className="w-3 h-3 text-primary" />
<span className="text-xs font-mono text-foreground">SYSTEM v3.0</span>
<>
<style dangerouslySetInnerHTML={{ __html: scrollbarStyles }} />
<div className={`${getContainerHeight()} bg-gradient-to-br from-background via-background to-muted/20 border-t border-border shadow-2xl ${className}`}>
{isMinimized ? (
<div className="h-full flex items-center justify-between px-3 bg-gradient-to-r from-muted/50 to-secondary/50 border-b border-border backdrop-blur-sm cursor-pointer" onClick={toggleMinimize}>
<div className="flex items-center gap-3">
<div className="flex items-center gap-1.5">
<div className="w-2 h-2 bg-gradient-to-br from-green-400 to-emerald-500 rounded-full animate-pulse shadow-lg" />
<span className="text-xs font-mono text-foreground font-medium">TERMINAL READY</span>
</div>
<div className="flex items-center gap-2">
<HardDrive className="w-3 h-3 text-muted-foreground" />
<span className="text-xs font-mono text-muted-foreground">{formatTime(currentTime)}</span>
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
<div className="text-xs text-muted-foreground font-mono bg-secondary/50 px-1.5 py-0.5 rounded-full">
MSG: {messages.length}
</div>
</div>
<div className="flex-1 flex items-center px-2 border-b border-border bg-secondary/20">
<div className="flex items-center gap-1 overflow-x-auto w-full pb-1">
{shortcutGroups[0].map((shortcut, index) => (
<button
key={index}
className="flex items-center text-xs cursor-pointer hover:bg-accent hover:text-accent-foreground px-2 py-1 rounded transition-colors min-w-fit border border-border hover:border-primary bg-background"
onClick={shortcut.action}
>
<span className="font-bold text-primary mr-1">{formatShortcutKey(shortcut.key)}</span>
<span className="text-muted-foreground">{shortcut.label}</span>
</button>
))}
</div>
</div>
<div className="flex-1 flex items-center px-2 bg-secondary/20">
<div className="flex items-center gap-1 overflow-x-auto w-full pb-1">
{shortcutGroups[1].map((shortcut, index) => (
<button
key={index}
className="flex items-center text-xs cursor-pointer hover:bg-accent hover:text-accent-foreground px-2 py-1 rounded transition-colors min-w-fit border border-border hover:border-primary bg-background"
onClick={shortcut.action}
>
<span className="font-bold text-primary mr-1">{formatShortcutKey(shortcut.key)}</span>
<span className="text-muted-foreground">{shortcut.label}</span>
</button>
))}
</div>
<div className="text-xs text-muted-foreground font-mono bg-secondary/50 px-1.5 py-0.5 rounded-full">
{formatTime(currentTime)}
</div>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize={30} minSize={20} maxSize={50}>
<div className="h-full flex flex-col bg-card border-r border-border">
<div className="flex items-center justify-between p-2 bg-muted border-b border-border">
<div className="flex items-center gap-2">
<Terminal className="w-4 h-4 text-primary" />
<span className="text-sm font-mono text-foreground">INPUT</span>
</div>
<div className="text-xs text-muted-foreground font-mono">
CMD
</div>
</div>
<div className="flex-1 p-3 bg-background">
<div className="h-full flex flex-col">
<div className="text-xs text-muted-foreground mb-2 font-mono">
COMMAND LINE INTERFACE
</div>
<div className="flex-1 bg-card border border-input rounded-md p-3 font-mono text-sm">
<div className="flex items-start gap-2">
<span className="text-primary">></span>
<textarea
placeholder="Enter your command or query here..."
className="flex-1 bg-transparent text-foreground placeholder:text-muted-foreground border-0 outline-none resize-none min-h-20"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
}}
/>
) : (
<ResizablePanelGroup direction="horizontal" className="h-full">
<ResizablePanel defaultSize={30} minSize={20} maxSize={50}>
<div className="h-full flex flex-col bg-gradient-to-br from-card via-card to-secondary/20 shadow-inner">
<div className="flex items-center justify-between px-3 py-1 bg-gradient-to-r from-muted/80 to-secondary/80 border-b border-border/50 backdrop-blur-sm cursor-pointer" onClick={toggleMaximize}>
<div className="flex items-center gap-2">
<div className="p-0.5 bg-primary/10 rounded border border-primary/20">
<Cpu className="w-3 h-3 text-primary" />
</div>
<span className="text-xs font-mono text-foreground font-medium">STATUS</span>
</div>
</div>
<div className="mt-2 flex justify-between items-center">
<div className="text-xs text-muted-foreground font-mono">
Enter to send Shift+Enter for new line
</div>
<button
onClick={handleSendMessage}
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded px-3 py-1 text-xs font-mono transition-colors flex items-center gap-1"
>
<Send className="w-3 h-3" />
SEND
</button>
</div>
</div>
</div>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize={40} minSize={25}>
<div className="h-full flex flex-col bg-card">
<div className="flex items-center justify-between p-2 bg-muted border-b border-border">
<div className="flex items-center gap-2">
<Monitor className="w-4 h-4 text-primary" />
<span className="text-sm font-mono text-foreground">OUTPUT</span>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground font-mono">
MSG: {messages.length}
</span>
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
</div>
</div>
<div className="flex-1 p-3 overflow-y-auto bg-background font-mono text-sm">
{messages.length === 0 ? (
<div className="text-muted-foreground text-center py-8">
<div className="animate-pulse text-primary text-lg"> SYSTEM READY </div>
<div className="mt-2 text-xs">
<div>Waiting for input...</div>
<div className="mt-1 text-muted-foreground/70">
Use F-keys or Ctrl commands
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 text-xs font-mono text-muted-foreground">
<span className="inline-flex items-center gap-0.5">
<span className="relative w-1.5 h-1.5 rounded-sm bg-gradient-to-br from-yellow-400 to-orange-500 border border-yellow-600 inline-block shadow-sm">
<span className="absolute -top-0.5 -right-0.5 w-1.5 h-1.5 rounded-full bg-primary animate-pulse border border-background shadow-md" />
</span>
<span className="text-xs">Inbox</span>
</span>
<span className="mx-0.5 text-border">|</span>
<span className="inline-flex items-center gap-0.5">
<span className="w-1.5 h-1.5 rounded-sm bg-gradient-to-br from-green-400 to-emerald-500 border border-green-600 inline-block shadow-sm" />
<span className="text-xs">Tasks</span>
</span>
<span className="mx-0.5 text-border">|</span>
<span className="inline-flex items-center gap-0.5">
<span className="w-1.5 h-1.5 rounded-sm bg-gradient-to-br from-red-400 to-rose-500 border border-red-600 inline-block shadow-sm" />
<span className="text-xs">Meet</span>
</span>
<span className="mx-0.5 text-border">|</span>
<span className="inline-flex items-center gap-0.5">
<span className="w-1.5 h-1.5 rounded-sm bg-gradient-to-br from-blue-400 to-cyan-500 border border-blue-600 inline-block shadow-sm" />
<span className="text-xs">Chat</span>
</span>
</div>
</div>
</div>
) : (
<div className="space-y-3">
{messages.map((message, index) => (
<div key={index} className="group">
<div className="text-xs text-muted-foreground mb-1 flex justify-between">
<span>[{formatTime(message.timestamp)}] {message.sender.toUpperCase()}:</span>
<span>#{index + 1}</span>
<div className="flex-1 flex items-center px-2 py-0.5 border-b border-border/50 bg-gradient-to-r from-secondary/20 to-accent/10">
<div className="flex items-center gap-0.5 overflow-x-auto w-full">
{shortcutGroups[0].map((shortcut, index) => (
<button
key={index}
className="flex items-center text-xs cursor-pointer hover:bg-gradient-to-r hover:from-accent/50 hover:to-primary/20 hover:text-accent-foreground px-1.5 py-1 rounded transition-all duration-200 min-w-fit border border-border/50 hover:border-primary/50 bg-gradient-to-r from-background/80 to-background/60 backdrop-blur-sm shadow-sm hover:shadow-md transform hover:scale-105"
onClick={shortcut.action}
>
<span className="font-bold text-primary mr-1">{formatShortcutKey(shortcut.key)}</span>
<span className="text-muted-foreground">{shortcut.label}</span>
</button>
))}
</div>
</div>
<div className="flex-1 flex items-center px-2 py-0.5 bg-gradient-to-r from-secondary/20 to-accent/10">
<div className="flex items-center gap-0.5 overflow-x-auto w-full">
{shortcutGroups[1].map((shortcut, index) => (
<button
key={index}
className="flex items-center text-xs cursor-pointer hover:bg-gradient-to-r hover:from-accent/50 hover:to-primary/20 hover:text-accent-foreground px-1.5 py-1 rounded transition-all duration-200 min-w-fit border border-border/50 hover:border-primary/50 bg-gradient-to-r from-background/80 to-background/60 backdrop-blur-sm shadow-sm hover:shadow-md transform hover:scale-105"
onClick={shortcut.action}
>
<span className="font-bold text-primary mr-1">{formatShortcutKey(shortcut.key)}</span>
<span className="text-muted-foreground">{shortcut.label}</span>
</button>
))}
</div>
</div>
</div>
</ResizablePanel>
<ResizableHandle className="bg-gradient-to-b from-border to-primary/20 hover:from-primary/30 hover:to-primary/40 transition-all duration-200" />
<ResizablePanel defaultSize={30} minSize={20} maxSize={50}>
<div className="h-full flex flex-col bg-gradient-to-br from-card via-card to-secondary/20 border-r border-border/50 shadow-inner">
<div className="flex items-center justify-between px-3 py-1 bg-gradient-to-r from-muted/80 to-secondary/80 border-b border-border/50 backdrop-blur-sm cursor-pointer" onClick={toggleMinimize}>
<div className="flex items-center gap-2">
<div className="p-0.5 bg-primary/10 rounded border border-primary/20">
<Terminal className="w-3 h-3 text-primary" />
</div>
<span className="text-xs font-mono text-foreground font-medium">INPUT</span>
</div>
<div className="text-xs text-muted-foreground font-mono bg-background/50 px-1.5 py-0.5 rounded-full border border-border/50">
CMD
</div>
</div>
<div className="flex-1 p-2 bg-gradient-to-br from-background/50 to-secondary/20 backdrop-blur-sm">
<div className="h-full flex flex-col">
<div className="text-xs text-muted-foreground mb-2 font-mono bg-secondary/20 px-1.5 py-0.5 rounded">
COMMAND LINE INTERFACE
</div>
<div className="flex-1 bg-gradient-to-br from-card to-card/80 border border-input/50 rounded p-2 font-mono text-xs shadow-inner backdrop-blur-sm">
<div className="flex items-start gap-1">
<span className="text-primary font-bold">></span>
<textarea
placeholder="Enter your command or query here..."
className="flex-1 bg-transparent text-foreground placeholder:text-muted-foreground border-0 outline-none resize-none min-h-12"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
}}
/>
</div>
<div className={`p-3 rounded border transition-all group-hover:shadow-sm ${
message.sender === 'user'
? 'bg-primary/10 border-primary text-foreground'
: message.sender === 'system'
? 'bg-accent/50 border-accent text-accent-foreground'
: 'bg-secondary/50 border-secondary text-secondary-foreground'
}`}>
<div className="whitespace-pre-wrap break-words">
{message.text}
</div>
<div className="mt-2 flex justify-between items-center">
<div className="text-xs text-muted-foreground font-mono bg-secondary/20 px-1.5 py-0.5 rounded">
Enter to send Shift+Enter for new line
</div>
<button
onClick={handleSendMessage}
className="bg-gradient-to-r from-primary to-primary/90 text-primary-foreground hover:from-primary/90 hover:to-primary/80 rounded px-3 py-1 text-xs font-mono transition-all duration-200 flex items-center gap-1 shadow-lg hover:shadow-xl transform hover:scale-105"
>
<Send className="w-2.5 h-2.5" />
SEND
</button>
</div>
</div>
</div>
</div>
</ResizablePanel>
<ResizableHandle className="bg-gradient-to-b from-border to-primary/20 hover:from-primary/30 hover:to-primary/40 transition-all duration-200" />
<ResizablePanel defaultSize={40} minSize={25}>
<div className="h-full flex flex-col bg-gradient-to-br from-card via-card to-accent/10 shadow-inner">
<div className="flex items-center justify-between px-3 py-1 bg-gradient-to-r from-muted/80 to-secondary/80 border-b border-border/50 backdrop-blur-sm cursor-pointer" onClick={toggleMaximize}>
<div className="flex items-center gap-2">
<div className="p-0.5 bg-primary/10 rounded border border-primary/20">
<Monitor className="w-3 h-3 text-primary" />
</div>
<span className="text-xs font-mono text-foreground font-medium">OUTPUT</span>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground font-mono bg-background/50 px-1.5 py-0.5 rounded-full border border-border/50">
MSG: {messages.length}
</span>
<div className="w-2 h-2 bg-gradient-to-br from-green-400 to-emerald-500 rounded-full animate-pulse shadow-lg" />
</div>
</div>
<div className="flex-1 p-2 overflow-y-auto bg-gradient-to-br from-background/50 to-secondary/20 font-mono text-xs backdrop-blur-sm">
{messages.length === 0 ? (
<div className="text-muted-foreground text-center py-8">
<div className="animate-pulse text-primary text-sm font-bold mb-2"> SYSTEM READY </div>
<div className="mt-1 text-xs space-y-1">
<div className="bg-secondary/30 px-2 py-0.5 rounded-full inline-block">Waiting for input...</div>
<div className="text-muted-foreground/70 bg-secondary/20 px-2 py-0.5 rounded-full inline-block">
Use F-keys or Ctrl commands
</div>
</div>
</div>
))}
) : (
<div className="space-y-2">
{messages.map((message, index) => (
<div key={index} className="group">
<div className="text-xs text-muted-foreground mb-1 flex justify-between bg-secondary/20 px-1.5 py-0.5 rounded-t">
<span>[{formatTime(message.timestamp)}] {message.sender.toUpperCase()}:</span>
<span className="bg-primary/20 px-1 py-0.5 rounded-full text-primary">#{index + 1}</span>
</div>
<div className={`p-2 rounded-b rounded-tr shadow-sm transition-all duration-200 group-hover:shadow-lg border backdrop-blur-sm ${message.sender === 'user'
? 'bg-gradient-to-r from-primary/10 to-primary/5 border-primary/30 text-foreground'
: message.sender === 'system'
? 'bg-gradient-to-r from-accent/50 to-accent/30 border-accent/50 text-accent-foreground'
: 'bg-gradient-to-r from-secondary/50 to-secondary/30 border-secondary/50 text-secondary-foreground'
}`}>
<div className="whitespace-pre-wrap break-words">
{message.text}
</div>
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
<div className="flex items-center justify-between px-3 py-1 bg-muted border-t border-border">
<div className="text-xs text-muted-foreground font-mono">
Ready for input
</div>
<div className="flex items-center gap-2">
<div className="text-xs text-muted-foreground font-mono">
SCROLL: {messages.length > 10 ? 'AUTO' : 'MANUAL'}
<div className="flex items-center justify-between px-3 py-1 bg-gradient-to-r from-muted/50 to-secondary/50 border-t border-border/50 backdrop-blur-sm">
<div className="text-xs text-muted-foreground font-mono">
Ready for input
</div>
<div className="flex items-center gap-2">
<div className="text-xs text-muted-foreground font-mono bg-background/50 px-1.5 py-0.5 rounded-full border border-border/50">
SCROLL: {messages.length > 10 ? 'AUTO' : 'MANUAL'}
</div>
<div className="w-1 h-1 bg-gradient-to-br from-primary to-primary/70 rounded-full animate-pulse shadow-sm" />
</div>
</div>
<div className="w-1 h-1 bg-primary rounded-full animate-pulse" />
</div>
</div>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</div>
</ResizablePanel>
</ResizablePanelGroup>
)}
</div>
</>
);
};

View file

@ -1,7 +1,7 @@
import { Nav } from './client-nav';
import { ReactNode } from 'react';
import { ThemeProvider } from './theme-provider';
import Nav from './client-nav';
export default function RootLayout({ children }: { children: ReactNode }) {
return (

View file

@ -1,5 +1,9 @@
Pages
=====
Should work on mobile also
Use themes variables
--------------------

View file

@ -698,10 +698,18 @@ body {
left: -3rem;
}
.-right-1 {
right: -0.25rem;
}
.-right-12 {
right: -3rem;
}
.-top-1 {
top: -0.25rem;
}
.-top-12 {
top: -3rem;
}
@ -1017,6 +1025,10 @@ body {
display: block;
}
.inline-block {
display: inline-block;
}
.flex {
display: flex;
}
@ -1117,6 +1129,10 @@ body {
height: 2rem;
}
.h-80 {
height: 20rem;
}
.h-9 {
height: 2.25rem;
}
@ -1329,6 +1345,10 @@ body {
width: 200px;
}
.w-\[280px\] {
width: 280px;
}
.w-\[40\%\] {
width: 40%;
}
@ -1817,6 +1837,11 @@ body {
border-radius: 0.75rem;
}
.rounded-r-md {
border-top-right-radius: calc(var(--radius) - 2px);
border-bottom-right-radius: calc(var(--radius) - 2px);
}
.rounded-t-\[10px\] {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
@ -1883,6 +1908,14 @@ body {
border-color: var(--color-border);
}
.border-accent {
border-color: hsl(var(--accent));
}
.border-background {
border-color: hsl(var(--background));
}
.border-blue-100 {
--tw-border-opacity: 1;
border-color: rgb(219 234 254 / var(--tw-border-opacity));
@ -1933,6 +1966,11 @@ body {
border-color: rgb(34 197 94 / var(--tw-border-opacity));
}
.border-green-600 {
--tw-border-opacity: 1;
border-color: rgb(22 163 74 / var(--tw-border-opacity));
}
.border-input {
border-color: hsl(var(--input));
}
@ -1959,6 +1997,15 @@ body {
border-color: rgb(239 68 68 / var(--tw-border-opacity));
}
.border-red-600 {
--tw-border-opacity: 1;
border-color: rgb(220 38 38 / var(--tw-border-opacity));
}
.border-secondary {
border-color: hsl(var(--secondary));
}
.border-sidebar-border {
border-color: hsl(var(--sidebar-border));
}
@ -1967,6 +2014,11 @@ body {
border-color: transparent;
}
.border-yellow-600 {
--tw-border-opacity: 1;
border-color: rgb(202 138 4 / var(--tw-border-opacity));
}
.border-l-blue-500 {
--tw-border-opacity: 1;
border-left-color: rgb(59 130 246 / var(--tw-border-opacity));
@ -2007,6 +2059,10 @@ body {
background-color: hsl(var(--accent));
}
.bg-accent\/50 {
background-color: hsl(var(--accent) / 0.5);
}
.bg-background {
background-color: hsl(var(--background));
}
@ -2093,6 +2149,11 @@ body {
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
}
.bg-gray-800 {
--tw-bg-opacity: 1;
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
}
.bg-gray-900 {
--tw-bg-opacity: 1;
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
@ -2103,6 +2164,11 @@ body {
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
}
.bg-green-400 {
--tw-bg-opacity: 1;
background-color: rgb(74 222 128 / var(--tw-bg-opacity));
}
.bg-green-500 {
--tw-bg-opacity: 1;
background-color: rgb(34 197 94 / var(--tw-bg-opacity));
@ -2124,10 +2190,6 @@ body {
background-color: hsl(var(--muted-foreground));
}
.bg-muted\/30 {
background-color: hsl(var(--muted) / 0.3);
}
.bg-muted\/50 {
background-color: hsl(var(--muted) / 0.5);
}
@ -2167,6 +2229,11 @@ body {
background-color: rgb(168 85 247 / 0.2);
}
.bg-red-400 {
--tw-bg-opacity: 1;
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
}
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
@ -2180,6 +2247,14 @@ body {
background-color: hsl(var(--secondary));
}
.bg-secondary\/20 {
background-color: hsl(var(--secondary) / 0.2);
}
.bg-secondary\/50 {
background-color: hsl(var(--secondary) / 0.5);
}
.bg-sidebar {
background-color: hsl(var(--sidebar-background));
}
@ -2202,6 +2277,11 @@ body {
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
}
.bg-yellow-400 {
--tw-bg-opacity: 1;
background-color: rgb(250 204 21 / var(--tw-bg-opacity));
}
.bg-gradient-to-br {
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
}
@ -2387,6 +2467,10 @@ body {
padding-bottom: 2rem;
}
.pb-1 {
padding-bottom: 0.25rem;
}
.pb-3 {
padding-bottom: 0.75rem;
}
@ -2407,6 +2491,10 @@ body {
padding-left: 0.625rem;
}
.pl-3 {
padding-left: 0.75rem;
}
.pl-4 {
padding-left: 1rem;
}
@ -2431,6 +2519,10 @@ body {
padding-right: 0.625rem;
}
.pr-3 {
padding-right: 0.75rem;
}
.pr-4 {
padding-right: 1rem;
}
@ -2621,6 +2713,16 @@ body {
color: rgb(31 41 55 / var(--tw-text-opacity));
}
.text-green-300 {
--tw-text-opacity: 1;
color: rgb(134 239 172 / var(--tw-text-opacity));
}
.text-green-400 {
--tw-text-opacity: 1;
color: rgb(74 222 128 / var(--tw-text-opacity));
}
.text-green-500 {
--tw-text-opacity: 1;
color: rgb(34 197 94 / var(--tw-text-opacity));
@ -2645,6 +2747,10 @@ body {
color: hsl(var(--muted-foreground));
}
.text-muted-foreground\/70 {
color: hsl(var(--muted-foreground) / 0.7);
}
.text-pink-500 {
--tw-text-opacity: 1;
color: rgb(236 72 153 / var(--tw-text-opacity));
@ -2708,6 +2814,11 @@ body {
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.text-yellow-400 {
--tw-text-opacity: 1;
color: rgb(250 204 21 / var(--tw-text-opacity));
}
.text-yellow-500 {
--tw-text-opacity: 1;
color: rgb(234 179 8 / var(--tw-text-opacity));
@ -3102,6 +3213,15 @@ body {
border-color: hsl(var(--accent));
}
.hover\:border-green-500:hover {
--tw-border-opacity: 1;
border-color: rgb(34 197 94 / var(--tw-border-opacity));
}
.hover\:border-primary:hover {
border-color: hsl(var(--primary));
}
.hover\:bg-accent:hover {
background-color: hsl(var(--accent));
}
@ -3110,10 +3230,6 @@ body {
background-color: hsl(var(--accent) / 0.1);
}
.hover\:bg-accent\/50:hover {
background-color: hsl(var(--accent) / 0.5);
}
.hover\:bg-blue-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
@ -3151,6 +3267,10 @@ body {
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
}
.hover\:bg-green-900\/30:hover {
background-color: rgb(20 83 45 / 0.3);
}
.hover\:bg-muted:hover {
background-color: hsl(var(--muted));
}
@ -3191,6 +3311,11 @@ body {
color: hsl(var(--foreground));
}
.hover\:text-green-300:hover {
--tw-text-opacity: 1;
color: rgb(134 239 172 / var(--tw-text-opacity));
}
.hover\:text-muted-foreground:hover {
color: hsl(var(--muted-foreground));
}
@ -3385,6 +3510,12 @@ body {
opacity: 1;
}
.group:hover .group-hover\:shadow-sm {
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.group[data-collapsed=true] .group-\[\[data-collapsed\=true\]\]\:justify-center {
justify-content: center;
}
@ -3608,6 +3739,10 @@ body {
background-color: hsl(var(--accent));
}
.data-\[state\=active\]\:bg-accent[data-state=active] {
background-color: hsl(var(--accent));
}
.data-\[state\=active\]\:bg-background[data-state=active] {
background-color: hsl(var(--background));
}