Add new themes: Orange and XTree Gold
Some checks failed
GBCI / build (push) Failing after 10m45s

- Introduced a new CSS theme for Orange, featuring a modern color palette with distinct foreground and background colors.
- Added an XTree Gold theme that emulates the classic 1980s DOS interface, complete with authentic colors and styles for file management elements.
- Both themes include variables for customization and specific styles for various UI components such as cards, popovers, and menus.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-06-28 19:30:35 -03:00
parent 3f60e58e23
commit 2a7aa20c4d
25 changed files with 3823 additions and 738 deletions

View file

@ -1,8 +1,976 @@
'use client'
"use client";
import React, { useState, useMemo } from 'react';
import {
Calendar, ChevronLeft, ChevronRight, Plus, Search, Settings,
Clock, Users, MapPin, Bell, Repeat, Mail, Phone, Video,
Edit3, Trash2, Copy, Forward, Reply, MoreHorizontal,
Filter, RefreshCw, Print, Import, Export, Share2,
ChevronDown, User, Building, Globe, AlertCircle,
CheckCircle2, X, Eye, EyeOff, Star, Flag, Tag
} from 'lucide-react';
import { cn } from "@/lib/utils";
export default function MainPage() {
return (
<div className="flex flex-col h-screen bg-gray-50">
</div>
)
}
// Sample calendar data
const sampleEvents = [
{
id: 1,
title: "Team Standup",
start: "2025-06-28T09:00:00",
end: "2025-06-28T09:30:00",
type: "meeting",
location: "Conference Room A",
attendees: ["john@company.com", "jane@company.com", "mike@company.com"],
description: "Daily team synchronization meeting",
priority: "high",
status: "accepted",
organizer: "sarah@company.com",
category: "work",
recurring: true
},
{
id: 2,
title: "Client Presentation",
start: "2025-06-28T14:00:00",
end: "2025-06-28T15:30:00",
type: "meeting",
location: "Board Room",
attendees: ["client@external.com", "manager@company.com"],
description: "Q2 results presentation to key client",
priority: "high",
status: "tentative",
organizer: "me@company.com",
category: "important",
recurring: false
},
{
id: 3,
title: "Lunch with Marketing Team",
start: "2025-06-28T12:00:00",
end: "2025-06-28T13:00:00",
type: "personal",
location: "Downtown Cafe",
attendees: ["marketing@company.com"],
description: "Monthly team lunch",
priority: "normal",
status: "accepted",
organizer: "marketing@company.com",
category: "social",
recurring: true
},
{
id: 4,
title: "Project Review",
start: "2025-06-29T10:00:00",
end: "2025-06-29T11:00:00",
type: "meeting",
location: "Online",
attendees: ["team@company.com"],
description: "Weekly project status review",
priority: "normal",
status: "accepted",
organizer: "me@company.com",
category: "work",
recurring: true
},
{
id: 5,
title: "Doctor Appointment",
start: "2025-06-30T15:00:00",
end: "2025-06-30T16:00:00",
type: "personal",
location: "Medical Center",
attendees: [],
description: "Annual checkup",
priority: "normal",
status: "accepted",
organizer: "me@company.com",
category: "personal",
recurring: false
}
];
const categoriesData = [
{ name: "Work", color: "blue", visible: true, count: 12 },
{ name: "Personal", color: "green", visible: true, count: 5 },
{ name: "Important", color: "red", visible: true, count: 3 },
{ name: "Social", color: "purple", visible: true, count: 8 },
{ name: "Travel", color: "orange", visible: false, count: 2 },
{ name: "Family", color: "pink", visible: true, count: 4 }
];
// Date utilities
const formatDate = (date) => {
return new Intl.DateTimeFormat('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date);
};
const formatTime = (dateString) => {
return new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
}).format(new Date(dateString));
};
const formatDateShort = (date) => {
return new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric'
}).format(date);
};
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
ContextMenuSeparator,
} from "@/components/ui/context-menu";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
TooltipProvider,
} from "@/components/ui/tooltip";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import Footer from '../footer';
const CalendarSidebar = ({ isCollapsed, categories, onCategoryToggle, currentDate, onDateSelect }) => {
const [selectedCategories, setSelectedCategories] = useState(
categories.filter(cat => cat.visible).map(cat => cat.name)
);
const toggleCategory = (categoryName) => {
const newSelected = selectedCategories.includes(categoryName)
? selectedCategories.filter(name => name !== categoryName)
: [...selectedCategories, categoryName];
setSelectedCategories(newSelected);
onCategoryToggle(categoryName);
};
const generateCalendarDays = () => {
const today = new Date();
const currentMonth = today.getMonth();
const currentYear = today.getFullYear();
const firstDay = new Date(currentYear, currentMonth, 1);
const lastDay = new Date(currentYear, currentMonth + 1, 0);
const startDate = new Date(firstDay);
startDate.setDate(startDate.getDate() - firstDay.getDay());
const days = [];
for (let i = 0; i < 42; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
days.push(date);
}
return days;
};
const calendarDays = generateCalendarDays();
const today = new Date();
const quickActions = [
{ icon: Plus, label: "New Event", action: "new-event" },
{ icon: Users, label: "New Meeting", action: "new-meeting" },
];
if (isCollapsed) {
return (
<div className="p-2 space-y-2 bg-card border-r border-border">
{quickActions.map((action, index) => (
<Tooltip key={index} delayDuration={0}>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" className="w-9 h-9 bg-secondary hover:bg-secondary/80">
<action.icon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="right" className="bg-popover text-popover-foreground border-border">
{action.label}
</TooltipContent>
</Tooltip>
))}
</div>
);
}
return (
<div className="flex flex-col h-full bg-card border-r border-border w-[200px]">
{/* Quick Actions */}
<div className="p-3 border-b border-border bg-secondary">
<h3 className="text-sm font-semibold text-foreground mb-2 font-mono">Quick Actions</h3>
<div className="space-y-1">
{quickActions.map((action, index) => (
<Button
key={index}
variant="ghost"
size="sm"
className="w-full justify-start text-xs h-8 hover:bg-secondary/80 bg-secondary"
>
<action.icon className="h-3 w-3 mr-2" />
{action.label}
</Button>
))}
</div>
</div>
{/* Mini Calendar */}
<div className="p-3 border-b border-border bg-secondary">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-semibold text-foreground font-mono">
{today.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
</h3>
<div className="flex gap-1">
<Button variant="ghost" size="icon" className="h-6 w-6 bg-secondary hover:bg-secondary/80">
<ChevronLeft className="h-3 w-3" />
</Button>
<Button variant="ghost" size="icon" className="h-6 w-6 bg-secondary hover:bg-secondary/80">
<ChevronRight className="h-3 w-3" />
</Button>
</div>
</div>
<div className="grid grid-cols-7 gap-1 text-xs font-mono">
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(day => (
<div key={day} className="text-center text-foreground font-medium p-1">
{day}
</div>
))}
{calendarDays.map((date, index) => {
const isToday = date.toDateString() === today.toDateString();
const isCurrentMonth = date.getMonth() === today.getMonth();
return (
<button
key={index}
onClick={() => onDateSelect(date)}
className={cn(
"p-1 text-center rounded-none hover:bg-secondary/80 transition-colors font-mono",
isToday && "bg-primary text-primary-foreground hover:bg-primary",
!isCurrentMonth && "text-muted-foreground",
isCurrentMonth && !isToday && "text-foreground"
)}
>
{date.getDate()}
</button>
);
})}
</div>
</div>
{/* Categories */}
<div className="flex-1 p-3 bg-secondary">
<h3 className="text-sm font-semibold text-foreground mb-2 font-mono">My Calendars</h3>
<ScrollArea className="h-full">
<div className="space-y-1">
{categories.map((category) => (
<div
key={category.name}
className="flex items-center justify-between p-1 rounded-none hover:bg-secondary/80"
>
<button
onClick={() => toggleCategory(category.name)}
className="flex items-center gap-2 flex-1 text-left"
>
<div className="flex items-center gap-2">
{category.visible ? (
<Eye className="h-3 w-3 text-foreground" />
) : (
<EyeOff className="h-3 w-3 text-muted-foreground" />
)}
<div
className={cn(
"w-3 h-3 rounded-none",
category.color === 'blue' && "bg-blue-500",
category.color === 'green' && "bg-green-500",
category.color === 'red' && "bg-red-500",
category.color === 'purple' && "bg-purple-500",
category.color === 'orange' && "bg-orange-500",
category.color === 'pink' && "bg-pink-500"
)}
/>
</div>
<span className={cn(
"text-xs font-mono",
category.visible ? "text-foreground" : "text-muted-foreground"
)}>
{category.name}
</span>
</button>
<span className="text-xs text-foreground font-mono">({category.count})</span>
</div>
))}
</div>
</ScrollArea>
</div>
</div>
);
};
const EventCard = ({ event, onAction }) => {
const getCategoryColor = (category) => {
const colors = {
work: "bg-blue-500/20 border-l-blue-500 text-foreground",
personal: "bg-green-500/20 border-l-green-500 text-foreground",
important: "bg-red-500/20 border-l-red-500 text-foreground",
social: "bg-purple-500/20 border-l-purple-500 text-foreground"
};
return colors[category] || "bg-secondary border-l-muted text-foreground";
};
const getPriorityIcon = (priority) => {
if (priority === 'high') return <Flag className="h-3 w-3 text-red-500" />;
return null;
};
const getStatusIcon = (status) => {
switch (status) {
case 'accepted': return <CheckCircle2 className="h-3 w-3 text-green-500" />;
case 'tentative': return <AlertCircle className="h-3 w-3 text-yellow-500" />;
case 'declined': return <X className="h-3 w-3 text-red-500" />;
default: return null;
}
};
return (
<ContextMenu>
<ContextMenuTrigger asChild>
<div className={cn(
"p-2 rounded-none border-l-4 cursor-pointer hover:shadow-none transition-all mb-1 border border-border",
getCategoryColor(event.category)
)}>
<div className="flex items-start justify-between mb-1">
<div className="flex items-center gap-1">
{getPriorityIcon(event.priority)}
<h4 className="text-sm font-medium truncate font-mono">{event.title}</h4>
</div>
<div className="flex items-center gap-1">
{event.recurring && <Repeat className="h-3 w-3 text-foreground" />}
{getStatusIcon(event.status)}
</div>
</div>
<div className="text-xs text-foreground space-y-1 font-mono">
<div className="flex items-center gap-1">
<Clock className="h-3 w-3" />
{formatTime(event.start)} - {formatTime(event.end)}
</div>
{event.location && (
<div className="flex items-center gap-1">
<MapPin className="h-3 w-3" />
<span className="truncate">{event.location}</span>
</div>
)}
{event.attendees.length > 0 && (
<div className="flex items-center gap-1">
<Users className="h-3 w-3" />
<span>{event.attendees.length} attendees</span>
</div>
)}
</div>
</div>
</ContextMenuTrigger>
<ContextMenuContent className="bg-popover text-popover-foreground border-border font-mono">
<ContextMenuItem onClick={() => onAction('open', event)} className="hover:bg-primary hover:text-primary-foreground">
<Eye className="h-4 w-4 mr-2" />
Open
</ContextMenuItem>
<ContextMenuItem onClick={() => onAction('edit', event)} className="hover:bg-primary hover:text-primary-foreground">
<Edit3 className="h-4 w-4 mr-2" />
Edit
</ContextMenuItem>
<ContextMenuSeparator className="border-t border-border" />
<ContextMenuItem onClick={() => onAction('forward', event)} className="hover:bg-primary hover:text-primary-foreground">
<Forward className="h-4 w-4 mr-2" />
Forward
</ContextMenuItem>
<ContextMenuItem onClick={() => onAction('copy', event)} className="hover:bg-primary hover:text-primary-foreground">
<Copy className="h-4 w-4 mr-2" />
Copy
</ContextMenuItem>
<ContextMenuSeparator className="border-t border-border" />
<ContextMenuItem onClick={() => onAction('delete', event)} className="text-destructive hover:bg-primary hover:text-primary-foreground">
<Trash2 className="h-4 w-4 mr-2" />
Delete
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
);
};
const CalendarView = ({ view, events, currentDate, onEventAction }) => {
const filteredEvents = events.filter(event => {
const eventDate = new Date(event.start);
if (view === 'day') {
return eventDate.toDateString() === currentDate.toDateString();
} else if (view === 'week') {
const weekStart = new Date(currentDate);
weekStart.setDate(currentDate.getDate() - currentDate.getDay());
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6);
return eventDate >= weekStart && eventDate <= weekEnd;
} else if (view === 'month') {
return eventDate.getMonth() === currentDate.getMonth() &&
eventDate.getFullYear() === currentDate.getFullYear();
}
return true;
});
if (view === 'day') {
const hours = Array.from({ length: 24 }, (_, i) => i);
const dayEvents = filteredEvents.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
return (
<div className="flex flex-col h-full bg-background">
<div className="p-4 bg-card border-b border-border">
<h2 className="text-lg font-semibold text-foreground font-mono">
{formatDate(currentDate)}
</h2>
</div>
<ScrollArea className="flex-1">
<div className="p-4 space-y-2">
{dayEvents.length > 0 ? (
dayEvents.map(event => (
<EventCard
key={event.id}
event={event}
onAction={onEventAction}
/>
))
) : (
<div className="text-center py-8 text-foreground font-mono">
<Calendar className="h-12 w-12 mx-auto mb-2 text-muted-foreground" />
<p>No events scheduled for this day</p>
</div>
)}
</div>
</ScrollArea>
</div>
);
}
if (view === 'week') {
const weekStart = new Date(currentDate);
weekStart.setDate(currentDate.getDate() - currentDate.getDay());
const weekDays = Array.from({ length: 7 }, (_, i) => {
const day = new Date(weekStart);
day.setDate(weekStart.getDate() + i);
return day;
});
return (
<div className="flex flex-col h-full bg-background">
<div className="p-4 bg-card border-b border-border">
<h2 className="text-lg font-semibold text-foreground font-mono">
Week of {formatDateShort(weekStart)} - {formatDateShort(weekDays[6])}
</h2>
</div>
<div className="flex-1 grid grid-cols-7 gap-1 p-2">
{weekDays.map((day, index) => {
const dayEvents = filteredEvents.filter(event =>
new Date(event.start).toDateString() === day.toDateString()
);
const isToday = day.toDateString() === new Date().toDateString();
return (
<div key={index} className="border border-border rounded-none bg-card min-h-[200px]">
<div className={cn(
"p-2 text-center border-b border-border text-sm font-medium font-mono",
isToday ? "bg-primary text-primary-foreground" : "bg-secondary text-foreground"
)}>
<div>{day.toLocaleDateString('en-US', { weekday: 'short' })}</div>
<div className="text-lg">{day.getDate()}</div>
</div>
<div className="p-1 space-y-1">
{dayEvents.map(event => (
<div
key={event.id}
className={cn(
"p-1 rounded-none text-xs border-l-2 cursor-pointer hover:shadow-none border border-border font-mono",
event.category === 'work' && "bg-blue-500/20 border-l-blue-500",
event.category === 'personal' && "bg-green-500/20 border-l-green-500",
event.category === 'important' && "bg-red-500/20 border-l-red-500",
event.category === 'social' && "bg-purple-500/20 border-l-purple-500"
)}
onClick={() => onEventAction('open', event)}
>
<div className="font-medium truncate">{event.title}</div>
<div className="text-foreground">{formatTime(event.start)}</div>
</div>
))}
</div>
</div>
);
})}
</div>
</div>
);
}
// Month view
const monthStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
const monthEnd = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
const calendarStart = new Date(monthStart);
calendarStart.setDate(calendarStart.getDate() - monthStart.getDay());
const calendarDays = Array.from({ length: 42 }, (_, i) => {
const day = new Date(calendarStart);
day.setDate(calendarStart.getDate() + i);
return day;
});
return (
<div className="flex flex-col h-full bg-background">
<div className="p-4 bg-card border-b border-border">
<h2 className="text-lg font-semibold text-foreground font-mono">
{currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
</h2>
</div>
<div className="flex-1 grid grid-cols-7 gap-1 p-2">
{['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].map(day => (
<div key={day} className="p-2 text-center text-sm font-medium text-foreground bg-secondary border border-border font-mono">
{day.substring(0, 3)}
</div>
))}
{calendarDays.map((day, index) => {
const dayEvents = filteredEvents.filter(event =>
new Date(event.start).toDateString() === day.toDateString()
);
const isToday = day.toDateString() === new Date().toDateString();
const isCurrentMonth = day.getMonth() === currentDate.getMonth();
return (
<div key={index} className={cn(
"border border-border bg-card min-h-[100px] p-1",
!isCurrentMonth && "bg-secondary"
)}>
<div className={cn(
"text-sm font-medium mb-1 font-mono",
isToday ? "bg-primary text-primary-foreground rounded-none px-1" : "text-foreground",
!isCurrentMonth && "text-muted-foreground"
)}>
{day.getDate()}
</div>
<div className="space-y-1">
{dayEvents.slice(0, 3).map(event => (
<div
key={event.id}
className={cn(
"p-1 rounded-none text-xs border-l-2 cursor-pointer border border-border font-mono",
event.category === 'work' && "bg-blue-500/20 border-l-blue-500",
event.category === 'personal' && "bg-green-500/20 border-l-green-500",
event.category === 'important' && "bg-red-500/20 border-l-red-500",
event.category === 'social' && "bg-purple-500/20 border-l-purple-500"
)}
onClick={() => onEventAction('open', event)}
>
<div className="truncate font-medium">{event.title}</div>
</div>
))}
{dayEvents.length > 3 && (
<div className="text-xs text-foreground text-center font-mono">
+{dayEvents.length - 3} more
</div>
)}
</div>
</div>
);
})}
</div>
</div>
);
};
const EventDetails = ({ event }) => {
if (!event) {
return (
<div className="flex flex-col items-center justify-center h-full text-center text-foreground bg-background font-mono">
<Calendar className="w-12 h-12 mb-4 text-muted-foreground" />
<div className="text-lg font-medium">No event selected</div>
<div className="text-sm">Select an event to view details</div>
</div>
);
}
return (
<div className="flex flex-col h-full bg-background border-l border-border">
{/* Header */}
<div className="p-4 border-b border-border bg-card">
<div className="flex items-center justify-between mb-2">
<h3 className="text-lg font-semibold text-foreground font-mono">{event.title}</h3>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="bg-secondary hover:bg-secondary/80">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="bg-popover text-popover-foreground border-border font-mono">
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Edit3 className="h-4 w-4 mr-2" />
Edit
</DropdownMenuItem>
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Forward className="h-4 w-4 mr-2" />
Forward
</DropdownMenuItem>
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Reply className="h-4 w-4 mr-2" />
Reply
</DropdownMenuItem>
<DropdownMenuSeparator className="border-t border-border" />
<DropdownMenuItem className="text-destructive hover:bg-primary hover:text-primary-foreground">
<Trash2 className="h-4 w-4 mr-2" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Badge variant="outline" className={cn(
"rounded-none border border-border font-mono",
event.category === 'work' && "border-blue-500 text-foreground",
event.category === 'personal' && "border-green-500 text-foreground",
event.category === 'important' && "border-red-500 text-foreground",
event.category === 'social' && "border-purple-500 text-foreground"
)}>
{event.category}
</Badge>
</div>
{/* Content */}
<ScrollArea className="flex-1">
<div className="p-4 space-y-4 font-mono">
{/* Time */}
<div className="flex items-center gap-2">
<Clock className="h-4 w-4 text-foreground" />
<div>
<div className="font-medium">
{formatTime(event.start)} - {formatTime(event.end)}
</div>
<div className="text-sm text-foreground">
{new Date(event.start).toLocaleDateString('en-US', {
weekday: 'long',
month: 'long',
day: 'numeric',
year: 'numeric'
})}
</div>
</div>
</div>
{/* Location */}
{event.location && (
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4 text-foreground" />
<span className="text-foreground">{event.location}</span>
</div>
)}
{/* Organizer */}
<div className="flex items-center gap-2">
<User className="h-4 w-4 text-foreground" />
<div>
<div className="font-medium text-foreground">Organizer</div>
<div className="text-sm text-foreground">{event.organizer}</div>
</div>
</div>
{/* Attendees */}
{event.attendees.length > 0 && (
<div className="flex items-start gap-2">
<Users className="h-4 w-4 text-foreground mt-1" />
<div className="flex-1">
<div className="font-medium mb-1 text-foreground">Attendees ({event.attendees.length})</div>
<div className="space-y-1">
{event.attendees.map((attendee, index) => (
<div key={index} className="text-sm text-foreground flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-none"></div>
{attendee}
</div>
))}
</div>
</div>
</div>
)}
{/* Description */}
{event.description && (
<div>
<div className="font-medium mb-2 text-foreground">Description</div>
<div className="text-sm text-foreground leading-relaxed">
{event.description}
</div>
</div>
)}
{/* Properties */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">Priority</span>
<Badge variant={event.priority === 'high' ? 'destructive' : 'secondary'} className="rounded-none border border-border font-mono">
{event.priority}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">Status</span>
<Badge variant={
event.status === 'accepted' ? 'default' :
event.status === 'tentative' ? 'secondary' : 'destructive'
} className="rounded-none border border-border font-mono">
{event.status}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">Recurring</span>
<Badge variant={event.recurring ? 'default' : 'secondary'} className="rounded-none border border-border font-mono">
{event.recurring ? 'Yes' : 'No'}
</Badge>
</div>
</div>
</div>
</ScrollArea>
{/* Footer */}
<div className="p-4 border-t border-border flex justify-end gap-2">
<Button variant="outline" size="sm" className="rounded-none border border-border font-mono">
<X className="h-4 w-4 mr-2" />
Decline
</Button>
<Button variant="outline" size="sm" className="rounded-none border border-border font-mono">
<Clock className="h-4 w-4 mr-2" />
Tentative
</Button>
<Button size="sm" className="rounded-none border border-border bg-primary text-primary-foreground font-mono hover:bg-primary/90">
<CheckCircle2 className="h-4 w-4 mr-2" />
Accept
</Button>
</div>
</div>
);
};
const CalendarPage = () => {
const [currentDate, setCurrentDate] = useState(new Date());
const [selectedEvent, setSelectedEvent] = useState(null);
const [categories, setCategories] = useState(categoriesData);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const handleEventAction = (action, event) => {
switch (action) {
case 'open':
setSelectedEvent(event);
break;
case 'edit':
// Handle edit
break;
case 'delete':
// Handle delete
break;
default:
break;
}
};
const handleCategoryToggle = (categoryName) => {
setCategories(categories.map(cat =>
cat.name === categoryName ? { ...cat, visible: !cat.visible } : cat
));
};
const handleDateSelect = (date) => {
setCurrentDate(date);
};
const shortcuts = [
// Calendar actions row (unique qkeys)
[
{ key: 'Q', label: 'New Event', action: () => console.log('New Event') },
{ key: 'W', label: 'New Meeting', action: () => console.log('New Meeting') },
{ key: 'E', label: 'Edit Event', action: () => console.log('Edit Event') },
{ key: 'R', label: 'Refresh', action: () => console.log('Refresh') },
{ key: 'T', label: 'Today', action: () => setCurrentDate(new Date()) },
{ key: 'Y', label: 'Delete Event', action: () => console.log('Delete Event') },
{ key: 'U', label: 'Duplicate', action: () => console.log('Duplicate Event') },
{ key: 'I', label: 'Invite', action: () => console.log('Invite Attendees') },
{ key: 'O', label: 'Open Event', action: () => console.log('Open Event') },
{ key: 'P', label: 'Print', action: () => console.log('Print Calendar') },
],
// Navigation/info row (unique qkeys)
[
{ key: 'A', label: 'Accept', action: () => console.log('Accept Invitation') },
{ key: 'S', label: 'Send', action: () => console.log('Send Invitation') },
{ key: 'D', label: 'Decline', action: () => console.log('Decline Invitation') },
{ key: 'G', label: 'Go to Date', action: () => console.log('Go to Date') },
{ key: 'H', label: 'Help', action: () => console.log('Help') },
{ key: 'J', label: 'Share', action: () => console.log('Share Calendar') },
{ key: 'K', label: 'Show/Hide Weekends', action: () => console.log('Toggle Weekends') },
{ key: 'L', label: 'List View', action: () => console.log('List View') },
{ key: 'Z', label: 'Month View', action: () => console.log('Month View') },
{ key: 'X', label: 'Week View', action: () => console.log('Week View') },
]
];
return (
<div className="flex flex-col h-[calc(100vh-50px)]">
<div className="flex h-screen bg-background font-mono">
{/* Sidebar */}
<CalendarSidebar
isCollapsed={sidebarCollapsed}
categories={categories}
onCategoryToggle={handleCategoryToggle}
currentDate={currentDate}
onDateSelect={handleDateSelect}
/>
{/* Main Content */}
<div className="flex-1 flex flex-col">
{/* Toolbar */}
<div className="p-2 border-b border-border flex items-center justify-between bg-card">
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
className="bg-secondary hover:bg-secondary/80"
>
{sidebarCollapsed ? (
<ChevronRight className="h-4 w-4" />
) : (
<ChevronLeft className="h-4 w-4" />
)}
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setCurrentDate(new Date())}
className="bg-secondary hover:bg-secondary/80 font-mono"
>
Today
</Button>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
onClick={() => {
const newDate = new Date(currentDate);
newDate.setDate(currentDate.getDate() - 1);
setCurrentDate(newDate);
}}
className="bg-secondary hover:bg-secondary/80"
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => {
const newDate = new Date(currentDate);
newDate.setDate(currentDate.getDate() + 1);
setCurrentDate(newDate);
}}
className="bg-secondary hover:bg-secondary/80"
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
<div className="text-sm font-medium ml-2 text-foreground font-mono">
{formatDate(currentDate)}
</div>
</div>
<div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="bg-secondary hover:bg-secondary/80 border-border rounded-none font-mono">
<Settings className="h-4 w-4 mr-2" />
Settings
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="bg-popover text-popover-foreground border-border font-mono">
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<RefreshCw className="h-4 w-4 mr-2" />
Refresh
</DropdownMenuItem>
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Filter className="h-4 w-4 mr-2" />
Filters
</DropdownMenuItem>
<DropdownMenuSeparator className="border-t border-border" />
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Print className="h-4 w-4 mr-2" />
Print
</DropdownMenuItem>
<DropdownMenuItem className="hover:bg-primary hover:text-primary-foreground">
<Share2 className="h-4 w-4 mr-2" />
Share
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{/* Calendar Area */}
<div className="flex-1 flex">
{/* Three calendar views on the left */}
<div className="w-[60%] flex flex-col border-r border-border">
<div className="h-1/3 border-b border-border">
<CalendarView
view="day"
events={sampleEvents}
currentDate={currentDate}
onEventAction={handleEventAction}
/>
</div>
<div className="h-1/3 border-b border-border">
<CalendarView
view="week"
events={sampleEvents}
currentDate={currentDate}
onEventAction={handleEventAction}
/>
</div>
<div className="h-1/3">
<CalendarView
view="month"
events={sampleEvents}
currentDate={currentDate}
onEventAction={handleEventAction}
/>
</div>
</div>
{/* Event details on the right */}
<div className="w-[40%]">
<EventDetails event={selectedEvent} />
</div>
</div>
</div>
</div>
<Footer shortcuts={shortcuts} />
</div>
);
};
export default CalendarPage;

View file

@ -7,16 +7,16 @@ import { useTheme } from './theme-provider';
const examples = [
{ name: "Chat", href: "/chat", color: "#25D366" }, // WhatsApp green
{ name: "Dashboard", href: "/dashboard", color: "#6366F1" }, // Indigo
{ name: "Paper", href: "/paper", color: "#6366F1" }, // Indigo
{ name: "Mail", href: "/mail", color: "#FFD700" }, // Outlook yellow
{ name: "Calendar", href: "/calendar", color: "#1DB954" }, // Spotify green
{ name: "Meet", href: "/meet", color: "#059669" }, // Google Meet green
{ name: "Drive", href: "/drive", color: "#10B981" }, // Emerald green
{ name: "Editor", href: "/editor", color: "#2563EB" }, // Word blue
{ name: "Player", href: "/player", color: "Yellow" }, // YouTube red
{ name: "Tables", href: "/tables", color: "#8B5CF6" }, // Purple
{ name: "Media", href: "/media", color: "Yellow" },
{ name: "News", href: "/news", color: "blue" },
{ name: "Templates", href: "/templates", color: "#F59E0B" }, // Amber
{ name: "Dashboard", href: "/dashboard", color: "#6366F1" }, // Indigo
{ name: "Sources", href: "/sources", color: "#F59E0B" }, // Amber
{ name: "Settings", href: "/settings", color: "#6B7280" }, // Gray
];
@ -145,6 +145,7 @@ export function Nav() {
case 'seasidepostcard': return '🏖️';
case 'typewriter': return '⌨️';
case 'jazzage': return '🎷';
case 'xtreegold': return 'X';
default: return '🎨';
}
};
@ -263,8 +264,9 @@ export function Nav() {
right: 0;
z-index: 50;
background: hsl(var(--background));
height: 40px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
height: auto;
min-height: 40px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
border-bottom: 1px solid hsl(var(--border));
}

View file

@ -1,54 +0,0 @@
import { useState, useEffect } from 'react';
import { core } from '@tauri-apps/api';
interface FileItem {
name: string;
path: string;
is_dir: boolean;
}
interface FileBrowserProps {
path: string;
onFileSelect?: (path: string) => void;
}
export function FileBrowser({ path, onFileSelect }: FileBrowserProps) {
const [files, setFiles] = useState<FileItem[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadFiles = async () => {
setLoading(true);
try {
const result = await core.invoke<FileItem[]>('list_files', { path });
setFiles(result);
} catch (error) {
console.error('Error listing files:', error);
} finally {
setLoading(false);
}
};
loadFiles();
}, [path]);
return (
<div className="flex-1 p-4">
<h3 className="text-lg font-semibold mb-4">File Browser: {path || 'Root'}</h3>
{loading ? (
<p>Loading...</p>
) : (
<ul className="space-y-1">
{files.map((file) => (
<li
key={file.path}
className={`p-2 hover:bg-gray-100 rounded cursor-pointer ${file.is_dir ? 'font-medium' : ''}`}
onClick={() => onFileSelect && onFileSelect(file.path)}
>
{file.is_dir ? '📁' : '📄'} {file.name}
</li>
))}
</ul>
)}
</div>
);
}

View file

@ -1,67 +0,0 @@
import { core } from '@tauri-apps/api';
import { useState } from 'react';
interface FileOperationsProps {
currentPath: string;
onRefresh: () => void;
}
export function FileOperations({ currentPath, onRefresh }: FileOperationsProps) {
const [uploadProgress, setUploadProgress] = useState(0);
const handleUpload = async () => {
try {
} catch (error) {
console.error('Upload failed:', error);
alert('Upload failed!');
} finally {
setUploadProgress(0);
}
};
const createFolder = async () => {
const folderName = prompt('Enter folder name:');
if (folderName) {
try {
await core.invoke('create_folder', {
path: currentPath,
name: folderName
});
onRefresh();
} catch (error) {
console.error('Error creating folder:', error);
alert('Failed to create folder');
}
}
};
return (
<div className="p-4 border-t border-gray-300 space-y-3">
<h3 className="text-lg font-semibold">File Operations</h3>
<div className="flex space-x-2">
<button
onClick={handleUpload}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Upload
</button>
<button
onClick={createFolder}
className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
>
New Folder
</button>
</div>
{uploadProgress > 0 && (
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div
className="bg-blue-600 h-2.5 rounded-full"
style={{ width: `${uploadProgress}%` }}
></div>
</div>
)}
</div>
);
}

View file

@ -1,59 +0,0 @@
import { useState, useEffect } from 'react';
import { core } from '@tauri-apps/api';
import { FileBrowser } from './FileBrowser';
interface FileItem {
name: string;
path: string;
is_dir: boolean;
}
interface FileTreeProps {
onSelect: (path: string) => void;
}
export function FileTree({ onSelect }: FileTreeProps) {
const [tree, setTree] = useState<FileItem[]>([]);
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
useEffect(() => {
const loadTree = async () => {
try {
const result = await core.invoke<FileItem[]>('list_files', { path: '' });
setTree(result.filter(item => item.is_dir));
} catch (error) {
console.error('Error loading file tree:', error);
}
};
loadTree();
}, []);
const toggleExpand = async (path: string) => {
setExpanded(prev => ({ ...prev, [path]: !prev[path] }));
onSelect(path);
};
return (
<div className="w-64 border-r border-gray-300 p-4 overflow-y-auto">
<h3 className="text-lg font-semibold mb-4">File Tree</h3>
<ul className="space-y-1">
{tree.map((item) => (
<li key={item.path} className="pl-2">
<div
onClick={() => toggleExpand(item.path)}
className="flex items-center cursor-pointer hover:bg-gray-100 p-1 rounded"
>
<span>{expanded[item.path] ? '📂' : '📁'}</span>
<span className="ml-2">{item.name}</span>
</div>
{expanded[item.path] && (
<div className="pl-4">
<FileBrowser path={item.path} />
</div>
)}
</li>
))}
</ul>
</div>
);
}

View file

@ -1,19 +1,23 @@
"use client";
import React, { useState, useMemo } from 'react';
import {
Search, Plus, Upload, Download, Trash2, Share, Star,
Grid, List, MoreVertical, Home, ChevronRight,
Folder, File, Image, Video, Music, FileText, Code, Database,
Package, Archive, Clock, Users, Eye, Edit3
import React, { useState, useMemo, useRef } from 'react';
import {
Search, Plus, Upload, Download, Trash2, Share, Star,
Grid, List, MoreVertical, Home, ChevronRight,
Folder, File, Image, Video, Music, FileText, Code, Database,
Package, Archive, Clock, Users, Eye, Edit3, Copy, Scissors,
FolderPlus, FilePlus, RefreshCw, Info, Lock, Unlock,
ArrowRight, ExternalLink, History, Settings
} from 'lucide-react';
import { cn } from "@/lib/utils";
import Footer from '../footer';
// Simple date formatting functions
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
};
@ -21,7 +25,7 @@ const formatDateTime = (dateString) => {
const date = new Date(dateString);
return date.toLocaleString('en-US', {
month: 'short',
day: 'numeric',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: '2-digit',
@ -35,13 +39,14 @@ const formatDistanceToNow = (date) => {
const diffMinutes = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffMinutes < 1) return 'now';
if (diffMinutes < 60) return `${diffMinutes}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays < 7) return `${diffDays}d ago`;
return formatDate(date);
};
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@ -52,7 +57,21 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
} from "@/components/ui/dropdown-menu";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
ContextMenuSeparator,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
} from "@/components/ui/context-menu";
import {
Tooltip,
TooltipContent,
@ -182,7 +201,7 @@ const getFileIcon = (item) => {
if (item.is_dir) {
return <Folder className="w-4 h-4 text-blue-600" />;
}
const iconMap = {
pdf: <FileText className="w-4 h-4 text-red-500" />,
xlsx: <Database className="w-4 h-4 text-green-600" />,
@ -195,7 +214,7 @@ const getFileIcon = (item) => {
mp4: <Video className="w-4 h-4 text-red-600" />,
mp3: <Music className="w-4 h-4 text-green-600" />
};
return iconMap[item.type] || <File className="w-4 h-4 text-gray-500" />;
};
@ -206,6 +225,94 @@ const formatFileSize = (bytes) => {
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
};
const FileContextMenu = ({ file, children, onAction }) => {
const contextMenuItems = [
{ icon: Eye, label: "Open", action: "open" },
{ icon: Download, label: "Download", action: "download" },
{ separator: true },
{
icon: Share, label: "Share", action: "share", submenu: [
{ icon: Users, label: "Share with team", action: "share-team" },
{ icon: ExternalLink, label: "Get link", action: "get-link" },
{ icon: Lock, label: "Restrict access", action: "restrict" },
]
},
{ icon: Star, label: file?.starred ? "Remove from starred" : "Add to starred", action: "star" },
{ separator: true },
{ icon: Copy, label: "Copy", action: "copy" },
{ icon: Scissors, label: "Cut", action: "cut" },
{ icon: Edit3, label: "Rename", action: "rename" },
{ separator: true },
{
icon: FolderPlus, label: "Move to", action: "move", submenu: [
{ icon: Folder, label: "Documents", action: "move-documents" },
{ icon: Folder, label: "Projects", action: "move-projects" },
{ icon: Folder, label: "Choose folder...", action: "move-choose" },
]
},
{ icon: Copy, label: "Make a copy", action: "duplicate" },
{ separator: true },
{ icon: History, label: "Version history", action: "history" },
{ icon: Info, label: "Details", action: "details" },
{ separator: true },
{ icon: Trash2, label: "Move to trash", action: "trash", destructive: true },
];
const renderContextMenuItem = (item, index) => {
if (item.separator) {
return <ContextMenuSeparator key={index} />;
}
if (item.submenu) {
return (
<ContextMenuSub key={index}>
<ContextMenuSubTrigger className="flex items-center gap-2">
<item.icon className="w-4 h-4" />
{item.label}
</ContextMenuSubTrigger>
<ContextMenuSubContent>
{item.submenu.map((subItem, subIndex) => (
<ContextMenuItem
key={subIndex}
onClick={() => onAction(subItem.action, file)}
className="flex items-center gap-2"
>
<subItem.icon className="w-4 h-4" />
{subItem.label}
</ContextMenuItem>
))}
</ContextMenuSubContent>
</ContextMenuSub>
);
}
return (
<ContextMenuItem
key={index}
onClick={() => onAction(item.action, file)}
className={cn(
"flex items-center gap-2",
item.destructive && "text-destructive focus:text-destructive"
)}
>
<item.icon className="w-4 h-4" />
{item.label}
</ContextMenuItem>
);
};
return (
<ContextMenu>
<ContextMenuTrigger asChild>
{children}
</ContextMenuTrigger>
<ContextMenuContent className="w-56">
{contextMenuItems.map(renderContextMenuItem)}
</ContextMenuContent>
</ContextMenu>
);
};
const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => {
const [expanded, setExpanded] = useState({ "": true, "projects": true });
@ -231,7 +338,7 @@ const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => {
return (
<div key={item.id}>
<button
<button
onClick={() => toggleExpand(path)}
className={cn(
"flex items-center w-full px-2 py-1.5 text-sm rounded-md hover:bg-accent transition-colors",
@ -265,7 +372,7 @@ const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => {
<div className="flex flex-col h-full">
<div className={cn("p-2", isCollapsed ? "px-1" : "")}>
<nav className="space-y-1">
{navLinks.map((link) =>
{navLinks.map((link) =>
isCollapsed ? (
<Tooltip key={link.path} delayDuration={0}>
<TooltipTrigger asChild>
@ -306,20 +413,18 @@ const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => {
);
};
const FileList = ({ path, searchTerm, filterType }) => {
const [selectedFile, setSelectedFile] = useState(null);
const FileList = ({ path, searchTerm, filterType, selectedFile, setSelectedFile, onContextAction }) => {
const files = useMemo(() => {
const currentItem = fileSystemData[path];
if (!currentItem || !currentItem.is_dir || !currentItem.children) return [];
let items = currentItem.children.map(childName => {
const childPath = path ? `${path}/${childName}` : childName;
return fileSystemData[childPath];
}).filter(Boolean);
if (searchTerm) {
items = items.filter(item =>
items = items.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}
@ -344,31 +449,36 @@ const FileList = ({ path, searchTerm, filterType }) => {
<ScrollArea className="h-full">
<div className="flex flex-col">
{files.map((item) => (
<button
<FileContextMenu
key={item.id}
className={cn(
"flex items-center gap-3 p-3 text-left text-sm transition-all hover:bg-accent border-b",
selectedFile?.id === item.id && "bg-muted"
)}
onClick={() => setSelectedFile(item)}
file={item}
onAction={onContextAction}
>
<div className="flex items-center gap-3 flex-1 min-w-0">
{getFileIcon(item)}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<div className="font-medium truncate">{item.name}</div>
{item.starred && <Star className="w-3 h-3 text-yellow-500 fill-current" />}
{item.shared && <Users className="w-3 h-3 text-blue-500" />}
</div>
<div className="text-xs text-muted-foreground">
{item.is_dir ? 'Folder' : formatFileSize(item.size)}
<button
className={cn(
"flex items-center gap-3 p-3 text-left text-sm transition-all hover:bg-accent border-b",
selectedFile?.id === item.id && "bg-muted"
)}
onClick={() => setSelectedFile(item)}
>
<div className="flex items-center gap-3 flex-1 min-w-0">
{getFileIcon(item)}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<div className="font-medium truncate">{item.name}</div>
{item.starred && <Star className="w-3 h-3 text-yellow-500 fill-current" />}
{item.shared && <Users className="w-3 h-3 text-blue-500" />}
</div>
<div className="text-xs text-muted-foreground">
{item.is_dir ? 'Folder' : formatFileSize(item.size)}
</div>
</div>
</div>
</div>
<div className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(item.modified), { addSuffix: true })}
</div>
</button>
<div className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(item.modified), { addSuffix: true })}
</div>
</button>
</FileContextMenu>
))}
</div>
</ScrollArea>
@ -481,88 +591,203 @@ export default function FileManager() {
const currentItem = fileSystemData[currentPath];
// Drive-specific keyboard shortcuts
// XTreeGold classic shortcut layout - two rows
// XTree/NC-style: only unique, non-browser, non-reserved keys for file ops
// No F1-F12, Ctrl+F, Ctrl+T, Ctrl+W, Ctrl+N, Ctrl+R, Ctrl+P, etc.
// Use Q, W, E, R, T, Y, U, I, O, P, A, S, D, G, H, J, K, L, Z, X, C, V, B, M with Ctrl/Shift if needed
const shortcuts = [
// File operations row (unique qkeys)
[
{ key: 'Q', label: 'Rename', action: () => console.log('Rename') },
{ key: 'W', label: 'View', action: () => console.log('View') },
{ key: 'E', label: 'Edit', action: () => console.log('Edit') },
{ key: 'R', label: 'Move', action: () => console.log('Move') },
{ key: 'T', label: 'MkDir', action: () => console.log('Make Directory') },
{ key: 'Y', label: 'Delete', action: () => console.log('Delete') },
{ key: 'U', label: 'Copy', action: () => console.log('Copy') },
{ key: 'I', label: 'Cut', action: () => console.log('Cut') },
{ key: 'O', label: 'Paste', action: () => console.log('Paste') },
{ key: 'P', label: 'Duplicate', action: () => console.log('Duplicate') },
],
// Navigation/info row (unique qkeys)
[
{ key: 'A', label: 'Select', action: () => console.log('Select') },
{ key: 'S', label: 'Select All', action: () => console.log('Select All') },
{ key: 'D', label: 'Deselect', action: () => console.log('Deselect') },
{ key: 'G', label: 'Details', action: () => console.log('Details') },
{ key: 'H', label: 'History', action: () => console.log('History') },
{ key: 'J', label: 'Share', action: () => console.log('Share') },
{ key: 'K', label: 'Star', action: () => console.log('Star') },
{ key: 'L', label: 'Download', action: () => console.log('Download') },
{ key: 'Z', label: 'Upload', action: () => console.log('Upload') },
{ key: 'X', label: 'Refresh', action: () => console.log('Refresh') },
]
];
const handleContextAction = (action, file) => {
console.log(`Context action: ${action}`, file);
// Handle context menu actions here
switch (action) {
case 'star':
// Toggle star status
console.log('Toggle star for', file.name);
break;
case 'share':
console.log('Share', file.name);
break;
case 'download':
console.log('Download', file.name);
break;
case 'trash':
console.log('Move to trash', file.name);
break;
case 'copy':
console.log('Copy', file.name);
break;
case 'cut':
console.log('Cut', file.name);
break;
case 'rename':
console.log('Rename', file.name);
break;
case 'duplicate':
console.log('Duplicate', file.name);
break;
case 'details':
console.log('Show details for', file.name);
setSelectedFile(file);
break;
default:
console.log('Unknown action:', action);
}
};
// Keyboard shortcut handler
React.useEffect(() => {
const handleKeyDown = (e) => {
const isCtrl = e.ctrlKey || e.metaKey;
const isShift = e.shiftKey;
if (isCtrl && e.key === 'n' && isShift) {
e.preventDefault();
console.log('New folder shortcut');
} else if (isCtrl && e.key === 'n') {
e.preventDefault();
console.log('New file shortcut');
} else if (isCtrl && e.key === 'u') {
e.preventDefault();
console.log('Upload shortcut');
} else if (isCtrl && e.key === 'f') {
e.preventDefault();
document.querySelector('input[placeholder="Search files"]')?.focus();
} else if (e.key === 'Delete' && selectedFile) {
e.preventDefault();
console.log('Delete shortcut');
} else if (e.key === 'F2' && selectedFile) {
e.preventDefault();
console.log('Rename shortcut');
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [selectedFile]);
return (
<TooltipProvider delayDuration={0}>
<ResizablePanelGroup direction="horizontal" className="h-full max-h-[800px]">
{/* Left Sidebar */}
<ResizablePanel
defaultSize={20}
collapsedSize={4}
collapsible={true}
minSize={15}
maxSize={30}
onCollapse={() => setIsCollapsed(true)}
onResize={() => setIsCollapsed(false)}
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300")}
>
<FolderTree
onSelect={setCurrentPath}
selectedPath={currentPath}
isCollapsed={isCollapsed}
/>
</ResizablePanel>
<ResizableHandle withHandle />
{/* Middle File List */}
<ResizablePanel defaultSize={50} minSize={30}>
<Tabs defaultValue="all" className="flex flex-col h-full">
<div className="flex items-center px-4 py-2">
<h1 className="text-xl font-bold">{currentItem?.name || 'My Drive'}</h1>
<TabsList className="ml-auto">
<TabsTrigger value="all">All</TabsTrigger>
<TabsTrigger value="starred">Starred</TabsTrigger>
</TabsList>
</div>
<Separator />
<div className="bg-background/95 p-4 backdrop-blur">
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search files"
className="pl-8"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<Select value={filterType} onValueChange={setFilterType}>
<SelectTrigger className="w-[140px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All items</SelectItem>
<SelectItem value="folders">Folders</SelectItem>
<SelectItem value="files">Files</SelectItem>
<SelectItem value="starred">Starred</SelectItem>
</SelectContent>
</Select>
<div className="flex flex-col h-[calc(100vh-40px)]"> {/* Adjust based on your nav height */}
<TooltipProvider delayDuration={0}>
<ResizablePanelGroup direction="horizontal" className="flex-1 min-h-0">
{/* Left Sidebar */}
<ResizablePanel
defaultSize={20}
collapsedSize={4}
collapsible={true}
minSize={15}
maxSize={30}
onCollapse={() => setIsCollapsed(true)}
onResize={() => setIsCollapsed(false)}
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300")}
>
<FolderTree
onSelect={setCurrentPath}
selectedPath={currentPath}
isCollapsed={isCollapsed}
/>
</ResizablePanel>
<ResizableHandle withHandle />
{/* Middle File List */}
<ResizablePanel defaultSize={50} minSize={30}>
<Tabs defaultValue="all" className="flex flex-col h-full">
<div className="flex items-center px-4 py-2">
<h1 className="text-xl font-bold">{currentItem?.name || 'My Drive'}</h1>
<TabsList className="ml-auto">
<TabsTrigger value="all">All</TabsTrigger>
<TabsTrigger value="starred">Starred</TabsTrigger>
</TabsList>
</div>
</div>
<TabsContent value="all" className="m-0 flex-1">
<FileList
path={currentPath}
searchTerm={searchTerm}
filterType={filterType}
/>
</TabsContent>
<TabsContent value="starred" className="m-0 flex-1">
<FileList
path={currentPath}
searchTerm={searchTerm}
filterType="starred"
/>
</TabsContent>
</Tabs>
</ResizablePanel>
<ResizableHandle withHandle />
{/* Right File Details */}
<ResizablePanel defaultSize={30} minSize={25}>
<FileDisplay file={selectedFile} />
</ResizablePanel>
</ResizablePanelGroup>
</TooltipProvider>
<Separator />
<div className="bg-background/95 p-4 backdrop-blur">
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search files"
className="pl-8"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<Select value={filterType} onValueChange={setFilterType}>
<SelectTrigger className="w-[140px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All items</SelectItem>
<SelectItem value="folders">Folders</SelectItem>
<SelectItem value="files">Files</SelectItem>
<SelectItem value="starred">Starred</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<TabsContent value="all" className="m-0 flex-1">
<FileList
path={currentPath}
searchTerm={searchTerm}
filterType={filterType}
selectedFile={selectedFile}
setSelectedFile={setSelectedFile}
onContextAction={handleContextAction}
/>
</TabsContent>
<TabsContent value="starred" className="m-0 flex-1">
<FileList
path={currentPath}
searchTerm={searchTerm}
filterType="starred"
selectedFile={selectedFile}
setSelectedFile={setSelectedFile}
onContextAction={handleContextAction}
/>
</TabsContent>
</Tabs>
</ResizablePanel>
<ResizableHandle withHandle />
{/* Right File Details */}
<ResizablePanel defaultSize={30} minSize={25}>
<FileDisplay file={selectedFile} />
</ResizablePanel>
</ResizablePanelGroup>
</TooltipProvider>
{/* Footer with Status Bar */}
<Footer shortcuts={shortcuts} />
</div>
);
}

132
app/footer.tsx Normal file
View file

@ -0,0 +1,132 @@
"use client";
import React, { useState, useMemo } from 'react';
import {
Search, Plus, Upload, Download, Trash2, Share, Star,
Grid, List, MoreVertical, Home, ChevronRight,
Folder, File, Image, Video, Music, FileText, Code, Database,
Package, Archive, Clock, Users, Eye, Edit3, Copy, Scissors,
FolderPlus, FilePlus, RefreshCw, Info, Lock, Unlock,
ArrowRight, ExternalLink, History, Settings,
MessageCircle, Send, X, Minimize2, Maximize2, Sparkles
} from 'lucide-react';
import { cn } from "@/lib/utils";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from "@/components/ui/tooltip";// General Bots-style Footer Component
const Footer = ({ className, shortcuts }) => {
const [isChatOpen, setIsChatOpen] = useState(false);
const [isChatMinimized, setIsChatMinimized] = useState(false);
// Expecting shortcuts as an array of two arrays: [ [row1], [row2] ]
const shortcutGroups = shortcuts || [[], []];
return (
<div className={cn("border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60", className)}>
{/* XTreeGold-style two-row footer */}
<div className="flex flex-col">
{/* First row - F1-F10 keys */}
<div className="flex h-8 items-center px-2 border-b">
<div className="flex items-center gap-4 overflow-x-auto">
{shortcutGroups[0].map((shortcut, index) => (
<div
key={index}
className="flex items-center text-xs cursor-pointer hover:bg-accent/50 px-1 py-0.5 rounded transition-colors min-w-fit"
onClick={shortcut.action}
>
<span className="font-bold text-blue-600 mr-1">{shortcut.key}</span>
<span className="text-muted-foreground">{shortcut.label}</span>
</div>
))}
</div>
</div>
{/* Second row - Other keys */}
<div className="flex h-8 items-center px-2">
<div className="flex items-center gap-4 overflow-x-auto">
{shortcutGroups[1].map((shortcut, index) => (
<div
key={index}
className="flex items-center text-xs cursor-pointer hover:bg-accent/50 px-1 py-0.5 rounded transition-colors min-w-fit"
onClick={shortcut.action}
>
<span className="font-bold text-blue-600 mr-1">{shortcut.key}</span>
<span className="text-muted-foreground">{shortcut.label}</span>
</div>
))}
</div>
{/* Right side - Status/AI Assistant */}
<div className="ml-auto flex items-center gap-2">
<Badge variant="outline" className="text-xs h-6">
<span className="text-green-500"></span> Ready
</Badge>
<Button
variant="ghost"
size="sm"
onClick={() => setIsChatOpen(!isChatOpen)}
className="flex items-center gap-2 text-xs h-6"
>
<MessageCircle className="w-3 h-3" />
<span>Assistant</span>
</Button>
</div>
</div>
</div>
{/* Chat Window (optional) */}
{isChatOpen && !isChatMinimized && (
<div className="border-t bg-card">
<div className="h-48 flex flex-col">
<div className="flex items-center justify-between p-2 border-b bg-muted/30">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
<span className="text-xs font-medium">General Bots Assistant</span>
</div>
<div className="flex gap-1">
<Button
variant="ghost"
size="icon"
onClick={() => setIsChatMinimized(!isChatMinimized)}
className="h-6 w-6"
>
{isChatMinimized ? <Maximize2 className="w-3 h-3" /> : <Minimize2 className="w-3 h-3" />}
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => setIsChatOpen(false)}
className="h-6 w-6"
>
<X className="w-3 h-3" />
</Button>
</div>
</div>
<ScrollArea className="flex-1 p-2">
<div className="text-xs text-muted-foreground p-4 text-center">
General Bots Assistant is ready to help with file operations.
</div>
</ScrollArea>
<div className="flex items-center gap-1 p-2 border-t">
<Input
placeholder="Ask about your files..."
className="flex-1 h-7 text-xs"
/>
<Button size="icon" className="h-7 w-7">
<Send className="w-3 h-3" />
</Button>
</div>
</div>
</div>
)}
</div>
);
};
export default Footer;

View file

@ -11,12 +11,15 @@ import { ThemeProvider } from './theme-provider';
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body className='flex flex-col min-h-screen'>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<Nav />
{children}
</ThemeProvider>
<body className="flex flex-col min-h-screen">
<div>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem={true}>
<Nav />
<div className="flex-1 overflow-hidden">
{children}
</div>
</ThemeProvider>
</div>
</body>
</html>
)

View file

@ -28,6 +28,8 @@
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable"
import Footer from '../footer';
import { ScrollArea } from "@/components/ui/scroll-area"
import {
Select,
@ -509,6 +511,40 @@
const defaultCollapsed = collapsed //? JSON.parse(collapsed.value) : undefined
// Drive-specific keyboard shortcuts
// XTreeGold classic shortcut layout - two rows
// XTree/NC-style: only unique, non-browser, non-reserved keys for file ops
// No F1-F12, Ctrl+F, Ctrl+T, Ctrl+W, Ctrl+N, Ctrl+R, Ctrl+P, etc.
// Use Q, W, E, R, T, Y, U, I, O, P, A, S, D, G, H, J, K, L, Z, X, C, V, B, M with Ctrl/Shift if needed
const shortcuts = [
// File operations row (unique qkeys)
[
{ key: 'Q', label: 'Rename', action: () => console.log('Rename') },
{ key: 'W', label: 'View', action: () => console.log('View') },
{ key: 'E', label: 'Edit', action: () => console.log('Edit') },
{ key: 'R', label: 'Move', action: () => console.log('Move') },
{ key: 'T', label: 'MkDir', action: () => console.log('Make Directory') },
{ key: 'Y', label: 'Delete', action: () => console.log('Delete') },
{ key: 'U', label: 'Copy', action: () => console.log('Copy') },
{ key: 'I', label: 'Cut', action: () => console.log('Cut') },
{ key: 'O', label: 'Paste', action: () => console.log('Paste') },
{ key: 'P', label: 'Duplicate', action: () => console.log('Duplicate') },
],
// Navigation/info row (unique qkeys)
[
{ key: 'A', label: 'Select', action: () => console.log('Select') },
{ key: 'S', label: 'Select All', action: () => console.log('Select All') },
{ key: 'D', label: 'Deselect', action: () => console.log('Deselect') },
{ key: 'G', label: 'Details', action: () => console.log('Details') },
{ key: 'H', label: 'History', action: () => console.log('History') },
{ key: 'J', label: 'Share', action: () => console.log('Share') },
{ key: 'K', label: 'Star', action: () => console.log('Star') },
{ key: 'L', label: 'Download', action: () => console.log('Download') },
{ key: 'Z', label: 'Upload', action: () => console.log('Upload') },
{ key: 'X', label: 'Refresh', action: () => console.log('Refresh') },
]
];
return (
<>
<div className="flex-col md:flex">
@ -519,7 +555,11 @@
defaultCollapsed={defaultCollapsed}
navCollapsedSize={4}
/>
</div>
{/* Footer with Status Bar */}
<Footer shortcuts={shortcuts} />
</>
)
}

View file

@ -1,8 +1,692 @@
'use client'
"use client";
import React, { useState, useRef, useEffect } from 'react';
import {
Search, Filter, Grid, List, Play, Pause, Volume2, VolumeX,
Eye, Bookmark, Share, Download, Clock, TrendingUp,
Globe, Calendar, Tag, ExternalLink, MoreHorizontal,
ChevronLeft, ChevronRight, Heart, MessageCircle,
Video, Image, Music, FileText, Rss, Archive,
Star, ThumbsUp, Settings, RefreshCw, Maximize,
User, MapPin, Zap, Briefcase, Gamepad2, Palette,
Users, Award, ShoppingBag, Plane, DollarSign,
Newspaper, AlertTriangle, Target, Mic, Camera
} from 'lucide-react';
import { cn } from "@/lib/utils";
export default function MainPage() {
return (
<div className="flex flex-col h-screen bg-gray-50">
</div>
)
// Mock news data - realistic news structure
const mockNews = [
{
id: 1,
headline: "Major Climate Summit Reaches Breakthrough Agreement on Carbon Reduction",
summary: "World leaders agree to unprecedented measures targeting 50% reduction in emissions by 2030, with binding commitments from major economies.",
content: "In a historic development at the Global Climate Summit, representatives from 195 countries have agreed to a comprehensive framework...",
author: "Sarah Chen",
publication: "Global Times",
publishedAt: "2025-01-15T14:30:00Z",
category: "Environment",
subcategory: "Climate Change",
readTime: "8 min read",
mediaType: "article",
imageUrl: "/api/placeholder/800/400",
videoUrl: null,
audioUrl: null,
tags: ["climate", "politics", "international", "environment"],
location: "Geneva, Switzerland",
breaking: true,
featured: true,
viewCount: 45670,
shareCount: 1240,
commentCount: 892,
bookmarkCount: 2130,
sentiment: "positive",
credibilityScore: 0.94
},
{
id: 2,
headline: "Tech Giant Announces Revolutionary AI Chip Architecture",
summary: "New quantum-enhanced processing unit promises 1000x performance improvement for AI workloads, reshaping the semiconductor industry.",
content: "Silicon Valley's leading tech company unveiled its latest innovation today, a groundbreaking AI chip that combines...",
author: "Marcus Rodriguez",
publication: "Tech Weekly",
publishedAt: "2025-01-15T12:45:00Z",
category: "Technology",
subcategory: "AI & Computing",
readTime: "6 min read",
mediaType: "video",
imageUrl: "/api/placeholder/800/400",
videoUrl: "/api/placeholder/video",
audioUrl: null,
videoDuration: "15:32",
tags: ["ai", "technology", "semiconductors", "innovation"],
location: "San Francisco, CA",
breaking: false,
featured: true,
viewCount: 78234,
shareCount: 2156,
commentCount: 445,
bookmarkCount: 3421,
sentiment: "positive",
credibilityScore: 0.91
},
{
id: 3,
headline: "Global Markets React to Federal Reserve Interest Rate Decision",
summary: "Stock markets surge as Fed maintains current rates, signaling confidence in economic recovery and inflation control measures.",
content: "Financial markets around the world responded positively to the Federal Reserve's decision to maintain...",
author: "Elena Vasquez",
publication: "Financial Herald",
publishedAt: "2025-01-15T11:20:00Z",
category: "Business",
subcategory: "Markets",
readTime: "5 min read",
mediaType: "article",
imageUrl: "/api/placeholder/800/400",
videoUrl: null,
audioUrl: null,
tags: ["finance", "fed", "markets", "economy"],
location: "New York, NY",
breaking: false,
featured: false,
viewCount: 34567,
shareCount: 876,
commentCount: 234,
bookmarkCount: 1456,
sentiment: "neutral",
credibilityScore: 0.96
},
{
id: 4,
headline: "Space Mission Discovers Evidence of Ancient Water on Mars",
summary: "NASA's latest rover findings reveal extensive underground water systems, raising new questions about potential for past life.",
content: "The Mars exploration mission has yielded its most significant discovery yet, with clear evidence of...",
author: "Dr. James Mitchell",
publication: "Science Today",
publishedAt: "2025-01-15T09:15:00Z",
category: "Science",
subcategory: "Space",
readTime: "10 min read",
mediaType: "multimedia",
imageUrl: "/api/placeholder/800/400",
videoUrl: "/api/placeholder/video",
audioUrl: "/api/placeholder/audio",
videoDuration: "8:45",
audioDuration: "12:30",
tags: ["space", "mars", "nasa", "discovery"],
location: "Pasadena, CA",
breaking: false,
featured: true,
viewCount: 92341,
shareCount: 4567,
commentCount: 1234,
bookmarkCount: 5678,
sentiment: "positive",
credibilityScore: 0.98
},
{
id: 5,
headline: "Championship Final Breaks Viewership Records Worldwide",
summary: "Historic match draws over 2 billion viewers globally, showcasing the growing international appeal of the sport.",
content: "Last night's championship final has set a new benchmark for global sports viewership...",
author: "Alex Thompson",
publication: "Sports Central",
publishedAt: "2025-01-15T08:30:00Z",
category: "Sports",
subcategory: "Football",
readTime: "4 min read",
mediaType: "video",
imageUrl: "/api/placeholder/800/400",
videoUrl: "/api/placeholder/video",
audioUrl: null,
videoDuration: "22:15",
tags: ["sports", "football", "championship", "records"],
location: "London, UK",
breaking: false,
featured: false,
viewCount: 156789,
shareCount: 8901,
commentCount: 3456,
bookmarkCount: 2345,
sentiment: "positive",
credibilityScore: 0.89
}
];
const categories = [
{ id: 'all', name: 'All News', icon: Newspaper, color: 'text-gray-600' },
{ id: 'breaking', name: 'Breaking', icon: AlertTriangle, color: 'text-red-600' },
{ id: 'politics', name: 'Politics', icon: Users, color: 'text-blue-600' },
{ id: 'technology', name: 'Technology', icon: Zap, color: 'text-purple-600' },
{ id: 'business', name: 'Business', icon: Briefcase, color: 'text-green-600' },
{ id: 'science', name: 'Science', icon: Target, color: 'text-indigo-600' },
{ id: 'sports', name: 'Sports', icon: Award, color: 'text-orange-600' },
{ id: 'entertainment', name: 'Entertainment', icon: Palette, color: 'text-pink-600' },
{ id: 'health', name: 'Health', icon: Heart, color: 'text-emerald-600' },
{ id: 'world', name: 'World', icon: Globe, color: 'text-cyan-600' }
];
const formatDate = (dateString) => {
const date = new Date(dateString);
const now = new Date();
const diffMs = now - date;
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffHours < 1) return 'Just now';
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays < 7) return `${diffDays}d ago`;
return date.toLocaleDateString();
};
const formatNumber = (num) => {
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`;
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`;
return num.toString();
};
const MediaPlayer = ({ article, onClose }) => {
const [isPlaying, setIsPlaying] = useState(false);
const [isMuted, setIsMuted] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const videoRef = useRef(null);
const audioRef = useRef(null);
const togglePlay = () => {
if (article.mediaType === 'video' && videoRef.current) {
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
}
setIsPlaying(!isPlaying);
}
};
const toggleMute = () => {
if (videoRef.current) {
videoRef.current.muted = !isMuted;
setIsMuted(!isMuted);
}
};
return (
<div className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4">
<div className="bg-background rounded-xl w-full max-w-4xl max-h-[90vh] overflow-hidden">
<div className="flex items-center justify-between p-4 border-b">
<h2 className="text-lg font-semibold truncate">{article.headline}</h2>
<button
onClick={onClose}
className="p-2 hover:bg-muted rounded-lg transition-colors"
>
×
</button>
</div>
<div className="relative">
{article.mediaType === 'video' && (
<div className="relative">
<video
ref={videoRef}
className="w-full h-auto max-h-[60vh]"
poster={article.imageUrl}
onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}
onLoadedMetadata={(e) => setDuration(e.target.duration)}
>
<source src={article.videoUrl} type="video/mp4" />
</video>
<div className="absolute bottom-4 left-4 flex items-center gap-2">
<button
onClick={togglePlay}
className="p-2 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
>
{isPlaying ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
</button>
<button
onClick={toggleMute}
className="p-2 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
>
{isMuted ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />}
</button>
<span className="text-white text-sm bg-black/50 px-2 py-1 rounded">
{article.videoDuration}
</span>
</div>
</div>
)}
{article.mediaType === 'audio' && (
<div className="p-8 text-center">
<div className="w-32 h-32 mx-auto mb-4 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center">
<Music className="w-16 h-16 text-white" />
</div>
<audio
ref={audioRef}
controls
className="w-full max-w-md mx-auto"
src={article.audioUrl}
/>
</div>
)}
</div>
<div className="p-4">
<div className="text-sm text-muted-foreground mb-2">
{article.author} {article.publication} {formatDate(article.publishedAt)}
</div>
<p className="text-sm leading-relaxed">{article.summary}</p>
</div>
</div>
</div>
);
};
const NewsCard = ({ article, layout = 'grid', onPlay, onBookmark, onShare }) => {
const getMediaIcon = () => {
switch (article.mediaType) {
case 'video': return <Video className="w-4 h-4" />;
case 'audio': return <Music className="w-4 h-4" />;
case 'multimedia': return <Camera className="w-4 h-4" />;
default: return <FileText className="w-4 h-4" />;
}
};
const getSentimentColor = () => {
switch (article.sentiment) {
case 'positive': return 'text-green-600';
case 'negative': return 'text-red-600';
default: return 'text-gray-600';
}
};
if (layout === 'list') {
return (
<div className="flex gap-4 p-4 border-b hover:bg-muted/50 transition-colors">
<div className="w-32 h-24 bg-muted rounded-lg overflow-hidden flex-shrink-0">
<img src={article.imageUrl} alt="" className="w-full h-full object-cover" />
{(article.mediaType === 'video' || article.mediaType === 'multimedia') && (
<div className="absolute inset-0 flex items-center justify-center">
<button
onClick={() => onPlay(article)}
className="p-2 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
>
<Play className="w-4 h-4" />
</button>
</div>
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
{article.breaking && (
<span className="bg-red-600 text-white text-xs px-2 py-1 rounded-full font-medium">
BREAKING
</span>
)}
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full">
{article.category}
</span>
<div className="flex items-center gap-1">
{getMediaIcon()}
<span className="text-xs text-muted-foreground">
{article.videoDuration || article.audioDuration || article.readTime}
</span>
</div>
</div>
<h3 className="font-semibold text-lg mb-2 line-clamp-2 hover:text-blue-600 cursor-pointer">
{article.headline}
</h3>
<p className="text-sm text-muted-foreground mb-2 line-clamp-2">
{article.summary}
</p>
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span className="font-medium">{article.author}</span>
<span></span>
<span>{article.publication}</span>
<span></span>
<span>{formatDate(article.publishedAt)}</span>
<span></span>
<span className="flex items-center gap-1">
<Eye className="w-3 h-3" />
{formatNumber(article.viewCount)}
</span>
</div>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => onBookmark(article)}
className="p-2 hover:bg-muted rounded-lg transition-colors"
>
<Bookmark className="w-4 h-4" />
</button>
<button
onClick={() => onShare(article)}
className="p-2 hover:bg-muted rounded-lg transition-colors"
>
<Share className="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
);
}
return (
<div className="bg-card border rounded-xl overflow-hidden hover:shadow-lg transition-all duration-300 group">
<div className="relative">
<img
src={article.imageUrl}
alt={article.headline}
className="w-full h-48 object-cover"
/>
{(article.mediaType === 'video' || article.mediaType === 'multimedia') && (
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={() => onPlay(article)}
className="p-4 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
>
<Play className="w-6 h-6" />
</button>
</div>
)}
<div className="absolute top-3 left-3 flex items-center gap-2">
{article.breaking && (
<span className="bg-red-600 text-white text-xs px-2 py-1 rounded-full font-medium">
BREAKING
</span>
)}
<span className="bg-black/70 text-white text-xs px-2 py-1 rounded-full">
{article.category}
</span>
</div>
<div className="absolute top-3 right-3 flex items-center gap-1">
{getMediaIcon()}
<span className="text-white text-xs bg-black/70 px-2 py-1 rounded-full">
{article.videoDuration || article.audioDuration || article.readTime}
</span>
</div>
</div>
<div className="p-4">
<h3 className="font-semibold text-lg mb-2 line-clamp-2 hover:text-blue-600 cursor-pointer">
{article.headline}
</h3>
<p className="text-sm text-muted-foreground mb-3 line-clamp-3">
{article.summary}
</p>
<div className="flex items-center justify-between text-xs text-muted-foreground mb-3">
<div className="flex items-center gap-2">
<span className="font-medium">{article.author}</span>
<span></span>
<span>{formatDate(article.publishedAt)}</span>
</div>
<div className="flex items-center gap-1">
<Eye className="w-3 h-3" />
{formatNumber(article.viewCount)}
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span className="flex items-center gap-1">
<Heart className="w-3 h-3" />
{formatNumber(article.shareCount)}
</span>
<span className="flex items-center gap-1">
<MessageCircle className="w-3 h-3" />
{formatNumber(article.commentCount)}
</span>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => onBookmark(article)}
className="p-1 hover:bg-muted rounded transition-colors"
>
<Bookmark className="w-4 h-4" />
</button>
<button
onClick={() => onShare(article)}
className="p-1 hover:bg-muted rounded transition-colors"
>
<Share className="w-4 h-4" />
</button>
<button className="p-1 hover:bg-muted rounded transition-colors">
<MoreHorizontal className="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
);
};
export default function NewsMediaBrowser() {
const [selectedCategory, setSelectedCategory] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
const [layout, setLayout] = useState('grid');
const [sortBy, setSortBy] = useState('latest');
const [articles, setArticles] = useState(mockNews);
const [selectedArticle, setSelectedArticle] = useState(null);
const [showPlayer, setShowPlayer] = useState(false);
const [bookmarkedArticles, setBookmarkedArticles] = useState(new Set());
const filteredArticles = articles.filter(article => {
const matchesCategory = selectedCategory === 'all' ||
article.category.toLowerCase() === selectedCategory ||
(selectedCategory === 'breaking' && article.breaking);
const matchesSearch = searchTerm === '' ||
article.headline.toLowerCase().includes(searchTerm.toLowerCase()) ||
article.summary.toLowerCase().includes(searchTerm.toLowerCase()) ||
article.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
return matchesCategory && matchesSearch;
});
const sortedArticles = [...filteredArticles].sort((a, b) => {
switch (sortBy) {
case 'latest':
return new Date(b.publishedAt) - new Date(a.publishedAt);
case 'popular':
return b.viewCount - a.viewCount;
case 'trending':
return b.shareCount - a.shareCount;
default:
return 0;
}
});
const handlePlay = (article) => {
setSelectedArticle(article);
setShowPlayer(true);
};
const handleBookmark = (article) => {
const newBookmarked = new Set(bookmarkedArticles);
if (newBookmarked.has(article.id)) {
newBookmarked.delete(article.id);
} else {
newBookmarked.add(article.id);
}
setBookmarkedArticles(newBookmarked);
};
const handleShare = (article) => {
navigator.share?.({
title: article.headline,
text: article.summary,
url: window.location.href + '/' + article.id
});
};
return (
<div className="flex flex-col h-screen bg-background">
{/* Header */}
<div className="border-b bg-background/95 backdrop-blur-sm sticky top-0 z-40">
<div className="px-6 py-4">
<div className="flex items-center justify-between mb-4">
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
News Center
</h1>
<div className="flex items-center gap-2">
<button className="flex items-center gap-2 px-3 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
<RefreshCw className="w-4 h-4" />
Refresh
</button>
<button className="p-2 hover:bg-muted rounded-lg transition-colors">
<Settings className="w-4 h-4" />
</button>
</div>
</div>
{/* Search and Filters */}
<div className="flex items-center gap-4 mb-4">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<input
type="text"
placeholder="Search news..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="latest">Latest</option>
<option value="popular">Most Popular</option>
<option value="trending">Trending</option>
</select>
<div className="flex items-center gap-1 bg-muted rounded-lg p-1">
<button
onClick={() => setLayout('grid')}
className={cn(
"p-2 rounded transition-colors",
layout === 'grid' ? "bg-background shadow-sm" : "hover:bg-background/50"
)}
>
<Grid className="w-4 h-4" />
</button>
<button
onClick={() => setLayout('list')}
className={cn(
"p-2 rounded transition-colors",
layout === 'list' ? "bg-background shadow-sm" : "hover:bg-background/50"
)}
>
<List className="w-4 h-4" />
</button>
</div>
</div>
{/* Categories */}
<div className="flex gap-2 overflow-x-auto pb-2">
{categories.map((category) => (
<button
key={category.id}
onClick={() => setSelectedCategory(category.id)}
className={cn(
"flex items-center gap-2 px-4 py-2 rounded-full whitespace-nowrap transition-all",
selectedCategory === category.id
? "bg-blue-600 text-white"
: "bg-muted hover:bg-muted/80"
)}
>
<category.icon className={cn("w-4 h-4", category.color)} />
{category.name}
</button>
))}
</div>
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-auto p-6">
<div className="max-w-7xl mx-auto">
{/* Featured Articles */}
{selectedCategory === 'all' && (
<div className="mb-8">
<h2 className="text-xl font-semibold mb-4">Featured Stories</h2>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{sortedArticles.filter(article => article.featured).slice(0, 2).map((article) => (
<NewsCard
key={article.id}
article={article}
layout="grid"
onPlay={handlePlay}
onBookmark={handleBookmark}
onShare={handleShare}
/>
))}
</div>
</div>
)}
{/* Article Grid/List */}
<div className="mb-4 flex items-center justify-between">
<h2 className="text-xl font-semibold">
{selectedCategory === 'all' ? 'All News' : categories.find(c => c.id === selectedCategory)?.name}
</h2>
<span className="text-sm text-muted-foreground">
{sortedArticles.length} articles
</span>
</div>
{layout === 'grid' ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{sortedArticles.map((article) => (
<NewsCard
key={article.id}
article={article}
layout="grid"
onPlay={handlePlay}
onBookmark={handleBookmark}
onShare={handleShare}
/>
))}
</div>
) : (
<div className="space-y-0 border rounded-xl overflow-hidden">
{sortedArticles.map((article) => (
<NewsCard
key={article.id}
article={article}
layout="list"
onPlay={handlePlay}
onBookmark={handleBookmark}
onShare={handleShare}
/>
))}
</div>
)}
</div>
</div>
{/* Media Player Modal */}
{showPlayer && selectedArticle && (
<MediaPlayer
article={selectedArticle}
onClose={() => setShowPlayer(false)}
/>
)}
</div>
);
}

View file

@ -1,8 +1,333 @@
'use client'
"use client";
import { useState, useRef, useEffect } from 'react';
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import TextStyle from '@tiptap/extension-text-style';
import Color from '@tiptap/extension-color';
import Highlight from '@tiptap/extension-highlight';
import TextAlign from '@tiptap/extension-text-align';
import Footer from '../footer'
export default function MainPage() {
return (
<div className="flex flex-col h-screen bg-gray-50">
</div>
)
}
import {
Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight,
Link, Highlighter, Type, Palette, Sparkles
} from 'lucide-react';
const SimplePaperNote = () => {
const [title, setTitle] = useState('Untitled');
const [isEditingTitle, setIsEditingTitle] = useState(false);
const titleInputRef = useRef(null);
const editor = useEditor({
extensions: [
StarterKit,
TextStyle,
Color,
Highlight.configure({ multicolor: true }),
TextAlign.configure({
types: ['heading', 'paragraph'],
}),
],
content: `
<p>Start writing your thoughts here...</p>
`,
editorProps: {
attributes: {
class: 'prose prose-invert max-w-none focus:outline-none min-h-[calc(100vh-8rem)] p-8 text-foreground',
},
},
});
const addLink = () => {
const previousUrl = editor?.getAttributes('link').href;
const url = window.prompt('Enter URL:', previousUrl);
if (url === null) return;
if (url === '') {
editor?.chain().focus().extendMarkRange('link').unsetLink().run();
return;
}
editor?.chain().focus().extendMarkRange('link').setLink({ href: url }).run();
};
useEffect(() => {
if (isEditingTitle && titleInputRef.current) {
titleInputRef.current.focus();
}
}, [isEditingTitle]);
if (!editor) {
return null;
}
// OneNote-like keyboard shortcuts (note editing & navigation)
// Two rows, unique qkeys, avoid browser/reserved keys
// File/Note operations row
const shortcuts = [
[
{ key: 'Q', label: 'Resume', action: () => {/* Implement resume logic here */ } },
{ key: 'W', label: 'Write', action: () => {/* Implement write logic here */ } },
{ key: 'E', label: 'Expand', action: () => {/* Implement expand logic here */ } },
{ key: 'R', label: 'One Word', action: () => {/* Implement one word logic here */ } },
{ key: 'T', label: 'As List', action: () => editor?.chain().focus().toggleBulletList().run() },
{ key: 'Y', label: 'As Mail', action: () => {/* Implement as mail logic here */ } },
{ key: 'U', label: 'Copy', action: () => document.execCommand('copy') },
{ key: 'I', label: 'Paste', action: () => document.execCommand('paste') },
{ key: 'O', label: 'Undo', action: () => editor?.chain().focus().undo().run() },
{ key: 'P', label: 'Redo', action: () => editor?.chain().focus().redo().run() },
],
[
{ key: 'A', label: 'Select', action: () => {/* Implement select logic here */ } },
{ key: 'S', label: 'Select All', action: () => editor?.chain().focus().selectAll().run() },
{ key: 'D', label: 'Deselect', action: () => {/* Implement deselect logic here */ } },
{ key: 'G', label: 'Random', action: () => {/* Implement insert image logic here */ } },
{ key: 'H', label: 'Idea', action: () => {/* Implement insert table logic here */ } },
{ key: 'J', label: 'Insert Link', action: addLink },
{ key: 'K', label: 'Highlight', action: () => editor?.chain().focus().toggleHighlight({ color: '#ffff00' }).run() },
{ key: 'L', label: 'To-Do', action: () => editor?.chain().focus().toggleTaskList?.().run?.() },
{ key: 'Z', label: 'Zoom In', action: () => {/* Implement zoom in logic here */ } },
{ key: 'X', label: 'Zoom Out', action: () => {/* Implement zoom out logic here */ } },
]
];
return (
<div className="min-h-screen bg-background text-foreground">
<div>
<div className="max-w-4xl mx-auto">
{/* Paper Shadow Effect */}
<div className="mx-4 my-8 bg-card rounded-lg shadow-2xl shadow-black/20 border border-border">
<EditorContent
editor={editor}
className="min-h-[calc(100vh-12rem)]"
/>
</div>
</div>
{/* Floating Selection Toolbar */}
{editor && (
<BubbleMenu
editor={editor}
tippyOptions={{
duration: 100,
placement: 'top',
animation: 'shift-away'
}}
>
<div className="flex items-center bg-card border border-border rounded-lg shadow-lg p-1">
{/* Text Formatting */}
<button
onClick={() => editor.chain().focus().toggleBold().run()}
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('bold') ? 'bg-primary text-primary-foreground' : 'text-foreground'
}`}
title="Bold"
>
<Bold className="h-4 w-4" />
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('italic') ? 'bg-primary text-primary-foreground' : 'text-foreground'
}`}
title="Italic"
>
<Italic className="h-4 w-4" />
</button>
<button
onClick={() => editor.chain().focus().toggleUnderline().run()}
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('underline') ? 'bg-primary text-primary-foreground' : 'text-foreground'
}`}
title="Underline"
>
<Underline className="h-4 w-4" />
</button>
<div className="w-px h-6 bg-border mx-1"></div>
{/* Text Alignment */}
<button
onClick={() => editor.chain().focus().setTextAlign('left').run()}
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive({ textAlign: 'left' }) ? 'bg-primary text-primary-foreground' : 'text-foreground'
}`}
title="Align Left"
>
<AlignLeft className="h-4 w-4" />
</button>
<button
onClick={() => editor.chain().focus().setTextAlign('center').run()}
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive({ textAlign: 'center' }) ? 'bg-primary text-primary-foreground' : 'text-foreground'
}`}
title="Align Center"
>
<AlignCenter className="h-4 w-4" />
</button>
<button
onClick={() => editor.chain().focus().setTextAlign('right').run()}
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive({ textAlign: 'right' }) ? 'bg-primary text-primary-foreground' : 'text-foreground'
}`}
title="Align Right"
>
<AlignRight className="h-4 w-4" />
</button>
<div className="w-px h-6 bg-border mx-1"></div>
{/* Highlight */}
<button
onClick={() => editor.chain().focus().toggleHighlight({ color: '#ffff00' }).run()}
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('highlight') ? 'bg-primary text-primary-foreground' : 'text-foreground'
}`}
title="Highlight"
>
<Highlighter className="h-4 w-4" />
</button>
{/* Link */}
<button
onClick={addLink}
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('link') ? 'bg-primary text-primary-foreground' : 'text-foreground'
}`}
title="Add Link"
>
<Link className="h-4 w-4" />
</button>
<div className="w-px h-6 bg-border mx-1"></div>
{/* Heading */}
<button
onClick={() => {
if (editor.isActive('heading')) {
editor.chain().focus().setParagraph().run();
} else {
editor.chain().focus().toggleHeading({ level: 2 }).run();
}
}}
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('heading') ? 'bg-primary text-primary-foreground' : 'text-foreground'
}`}
title="Heading"
>
<Type className="h-4 w-4" />
</button>
</div>
</BubbleMenu>
)}
</div>
{/* Custom Styles */}
<style jsx global>{`
.ProseMirror {
outline: none;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
font-size: 16px;
line-height: 1.7;
color: hsl(var(--foreground));
padding: 3rem;
min-height: calc(100vh - 12rem);
}
.ProseMirror h1 {
font-size: 2.5rem;
font-weight: 700;
margin: 2rem 0 1rem 0;
color: hsl(var(--primary));
}
.ProseMirror h2 {
font-size: 2rem;
font-weight: 600;
margin: 1.5rem 0 0.75rem 0;
color: hsl(var(--primary));
}
.ProseMirror h3 {
font-size: 1.5rem;
font-weight: 600;
margin: 1.25rem 0 0.5rem 0;
color: hsl(var(--primary));
}
.ProseMirror p {
margin: 0.75rem 0;
}
.ProseMirror a {
color: hsl(var(--accent));
text-decoration: underline;
text-underline-offset: 2px;
}
.ProseMirror a:hover {
color: hsl(var(--primary));
}
.ProseMirror mark {
background-color: #ffff0040;
border-radius: 2px;
padding: 0 2px;
}
.ProseMirror ul, .ProseMirror ol {
margin: 1rem 0;
padding-left: 1.5rem;
}
.ProseMirror li {
margin: 0.25rem 0;
}
.ProseMirror blockquote {
border-left: 4px solid hsl(var(--primary));
padding-left: 1rem;
margin: 1rem 0;
font-style: italic;
color: hsl(var(--muted-foreground));
}
.ProseMirror code {
background-color: hsl(var(--muted));
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.9em;
}
.ProseMirror pre {
background-color: hsl(var(--muted));
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
margin: 1rem 0;
}
.ProseMirror pre code {
background: none;
padding: 0;
}
/* Selection highlighting */
.ProseMirror ::selection {
background-color: hsl(var(--primary) / 0.2);
}
/* Placeholder styling */
.ProseMirror p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: hsl(var(--muted-foreground));
pointer-events: none;
height: 0;
}
`}</style>
<Footer shortcuts={shortcuts} />
</div>
);
};
export default SimplePaperNote;

897
app/sources/page.tsx Normal file
View file

@ -0,0 +1,897 @@
"use client";
import React, { useState } from 'react';
import {
Search, FileText, Newspaper, Presentation, Table,
File, Lightbulb, TrendingUp, Users, Code, Briefcase,
Shield, Heart, Gamepad2, Palette, Book, Calculator,
Globe, Zap, Clock, Star, ArrowRight, ExternalLink,
Copy, Download, Play, Server, Cpu, Cloud, Sun, ShoppingCart, UserPlus
} from 'lucide-react';
// Mock data for prompts
const promptCategories = [
{ name: "AI Development", count: 4, color: "--primary", icon: Code },
{ name: "Security", count: 8, color: "--destructive", icon: Shield },
{ name: "Programming", count: 6, color: "--secondary", icon: Code },
{ name: "Career & Employment", count: 3, color: "--accent", icon: Briefcase },
{ name: "Health & Therapy", count: 4, color: "--chart-1", icon: Heart },
{ name: "Games & Entertainment", count: 5, color: "--chart-2", icon: Gamepad2 },
{ name: "Art & Design", count: 4, color: "--chart-3", icon: Palette },
{ name: "Education", count: 6, color: "--chart-4", icon: Book },
{ name: "Business & Finance", count: 4, color: "--chart-5", icon: Calculator },
{ name: "Technology", count: 3, color: "--primary", icon: Globe }
];
const featuredPrompts = [
{ name: "Job Scout Pro", category: "Career & Employment", description: "AI-powered job hunting assistant", rating: 4.8 },
{ name: "Cyber Security Sentinel", category: "Security", description: "Advanced security analysis tool", rating: 4.9 },
{ name: "Code Copilot Pro", category: "Programming", description: "Intelligent coding companion", rating: 4.7 },
{ name: "Digital Synthia Companion", category: "AI & Chatbots", description: "Advanced AI conversation partner", rating: 4.6 }
];
// Mock data for templates
const templateCategories = [
{ name: "Documents", icon: FileText, count: 12, color: "--primary" },
{ name: "Presentations", icon: Presentation, count: 8, color: "--destructive" },
{ name: "Spreadsheets", icon: Table, count: 6, color: "--secondary" }
];
const featuredTemplates = [
{ name: "Business Proposal", type: "Document", description: "Professional business proposal template" },
{ name: "Pitch Deck", type: "Presentation", description: "Startup pitch presentation template" },
{ name: "Budget Tracker", type: "Spreadsheet", description: "Personal finance tracking spreadsheet" },
{ name: "Project Report", type: "Document", description: "Comprehensive project report template" }
];
// Mock data for news
const newsCategories = [
{ name: "Technology", count: 45, color: "--primary" },
{ name: "AI & ML", count: 38, color: "--chart-2" },
{ name: "Business", count: 52, color: "--secondary" },
{ name: "Science", count: 29, color: "--destructive" },
{ name: "Startups", count: 34, color: "--accent" }
];
const featuredNews = [
{
title: "OpenAI Announces GPT-5 with Revolutionary Capabilities",
source: "TechCrunch",
time: "2 hours ago",
category: "AI & ML",
summary: "The latest AI model shows unprecedented reasoning abilities..."
},
{
title: "Quantum Computing Breakthrough Achieves New Milestone",
source: "Nature",
time: "4 hours ago",
category: "Science",
summary: "Researchers demonstrate error-corrected quantum computation..."
},
{
title: "Tech Giants Report Q4 Earnings Exceed Expectations",
source: "Bloomberg",
time: "6 hours ago",
category: "Business",
summary: "Major technology companies show strong growth despite market concerns..."
}
];
// MCP Servers data
const mcpServers = [
{
name: "ModelScope MCP Playground",
description: "Exploring the Free Combination and Interaction of Open Source Models and MCP Server",
type: "Hosted",
category: "developer-tools",
usage: "1203",
icon: Server
},
{
name: "ModelScope MCP Tutorial",
description: "Learn about MCP, observe and discuss best practices",
type: "Hosted",
category: "knowledge-and-memory",
usage: "264",
icon: Book
},
{
name: "TONGYI Lingma Programming Agent",
description: "Supports the use of MCP tools",
type: "Hosted",
category: "developer-tools",
usage: "1203",
icon: Code
},
{
name: "Cherry Studio Integration",
description: "Integrate ModelScope-hosted MCP server in Cherry Studio",
type: "Hosted",
category: "developer-tools",
usage: "1203",
icon: Cpu
},
{
name: "fetch",
description: "Retrieve and process content from web pages, converting HTML to markdown",
type: "Hosted",
category: "search",
usage: "609",
icon: Cloud
},
{
name: "amap-maps",
description: "Location-based services using Amap Maps MCP server",
type: "Hosted",
category: "location-services",
usage: "71",
icon: Globe
},
{
name: "bing-cn-mcp-server",
description: "Bing search Chinese version",
type: "Hosted",
category: "search",
usage: "609",
icon: Search
},
{
name: "AllVoiceLab",
description: "Text-to-speech and video translation APIs",
type: "Local",
category: "entertainment-and-media",
usage: "106",
icon: Heart
},
{
name: "12306-mcp",
description: "Search for 12306 train tickets",
type: "Hosted",
category: "other",
usage: "294",
icon: Clock
},
{
name: "Jina-AI-MCP-Tools",
description: "Integrates with Jina AI Search Foundation APIs",
type: "Hosted",
category: "search",
usage: "609",
icon: TrendingUp
}
];
// LLM Tools data
const llmTools = [
{
name: "Enroll",
description: "User registration and account creation tool",
category: "User Management",
icon: UserPlus,
color: "--primary"
},
{
name: "How is the weather",
description: "Real-time weather information lookup",
category: "Location Services",
icon: Sun,
color: "--accent"
},
{
name: "Price of Product",
description: "Product price lookup and comparison",
category: "E-commerce",
icon: ShoppingCart,
color: "--secondary"
},
{
name: "Document Analyzer",
description: "Extract and analyze document content",
category: "Knowledge",
icon: FileText,
color: "--chart-2"
},
{
name: "Code Generator",
description: "Generate code snippets from descriptions",
category: "Development",
icon: Code,
color: "--primary"
},
{
name: "Data Visualizer",
description: "Create charts and visualizations from data",
category: "Analytics",
icon: TrendingUp,
color: "--chart-1"
}
];
export default function LabComponent() {
const [activeTab, setActiveTab] = useState('prompts');
const [selectedCategory, setSelectedCategory] = useState(null);
const renderPrompts = () => (
<div className="h-full flex flex-col" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
{/* Header */}
<div className="p-4 border-b" style={{ borderColor: 'var(--border)' }}>
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-bold flex items-center gap-2">
<Lightbulb className="h-6 w-6" style={{ color: 'var(--chart-4)' }} />
Prompt Library
</h2>
<div className="flex gap-2">
<button className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors" style={{ backgroundColor: 'var(--primary)', color: 'var(--primary-foreground)' }}>
Create Custom
</button>
</div>
</div>
{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4" style={{ color: 'var(--muted-foreground)' }} />
<input
type="text"
placeholder="Search 1000+ prompts..."
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-2 focus:border-transparent"
style={{
borderColor: 'var(--border)',
backgroundColor: 'var(--background)',
color: 'var(--foreground)',
'--ring': 'var(--ring)'
}}
/>
</div>
</div>
<div className="flex-1 flex">
{/* Categories Sidebar */}
<div className="w-80 border-r p-4" style={{ borderColor: 'var(--border)', backgroundColor: 'var(--background)' }}>
<h3 className="font-semibold mb-4" style={{ color: 'var(--muted-foreground)' }}>Categories</h3>
<div className="space-y-2">
{promptCategories.map((category, index) => {
const IconComponent = category.icon;
return (
<button
key={index}
onClick={() => setSelectedCategory(category.name)}
className={`w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card ${
selectedCategory === category.name ? 'bg-card shadow-sm border-l-4' : ''
}`}
style={{
borderLeftColor: selectedCategory === category.name ? `var(${category.color})` : 'transparent',
backgroundColor: selectedCategory === category.name ? 'var(--card)' : '',
color: 'var(--card-foreground)'
}}
>
<div className="flex items-center gap-3">
<IconComponent className="h-4 w-4" style={{ color: `var(${category.color})` }} />
<span className="font-medium">{category.name}</span>
</div>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>
{category.count}
</span>
</button>
);
})}
</div>
{/* Featured Section */}
<div className="mt-8">
<h3 className="font-semibold mb-4 flex items-center gap-2" style={{ color: 'var(--muted-foreground)' }}>
<Star className="h-4 w-4" style={{ color: 'var(--chart-4)' }} />
Featured Prompts
</h3>
<div className="space-y-3">
{featuredPrompts.map((prompt, index) => (
<div key={index} className="p-3 rounded-lg shadow-sm border" style={{ backgroundColor: 'var(--card)', color: 'var(--card-foreground)', borderColor: 'var(--border)' }}>
<h4 className="font-medium text-sm">{prompt.name}</h4>
<p className="text-xs mt-1" style={{ color: 'var(--muted-foreground)' }}>{prompt.description}</p>
<div className="flex items-center justify-between mt-2">
<span className="text-xs px-2 py-1 rounded" style={{ backgroundColor: 'var(--primary)', color: 'var(--primary-foreground)' }}>
{prompt.category}
</span>
<div className="flex items-center gap-1">
<Star className="h-3 w-3 fill-current" style={{ color: 'var(--chart-4)' }} />
<span className="text-xs" style={{ color: 'var(--muted-foreground)' }}>{prompt.rating}</span>
</div>
</div>
</div>
))}
</div>
</div>
</div>
{/* Content Area */}
<div className="flex-1 p-6" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{[...Array(8)].map((_, index) => (
<div key={index} className="border rounded-lg p-4 transition-shadow" style={{ backgroundColor: 'var(--card)', color: 'var(--card-foreground)', borderColor: 'var(--border)' }}>
<div className="flex items-start justify-between mb-3">
<h3 className="font-semibold text-lg">Advanced Code Review Assistant</h3>
<div className="flex gap-2">
<button className="p-1 hover:bg-gray-100 rounded" style={{ '--hover-bg': 'var(--muted)' }}>
<Copy className="h-4 w-4" style={{ color: 'var(--muted-foreground)' }} />
</button>
<button className="p-1 hover:bg-gray-100 rounded" style={{ '--hover-bg': 'var(--muted)' }}>
<Download className="h-4 w-4" style={{ color: 'var(--muted-foreground)' }} />
</button>
</div>
</div>
<p className="text-sm mb-3" style={{ color: 'var(--muted-foreground)' }}>
AI assistant specialized in code review, security analysis, and optimization suggestions.
</p>
<div className="flex items-center justify-between">
<span className="text-xs px-2 py-1 rounded" style={{ backgroundColor: 'var(--chart-2)', color: 'var(--chart-2-foreground)' }}>Programming</span>
<button className="flex items-center gap-1 text-sm font-medium" style={{ color: 'var(--primary)' }}>
<Play className="h-3 w-3" />
Use Prompt
</button>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
const renderTemplates = () => (
<div className="h-full flex flex-col" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
{/* Header */}
<div className="p-4 border-b" style={{ borderColor: 'var(--border)' }}>
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-bold flex items-center gap-2">
<FileText className="h-6 w-6" style={{ color: 'var(--secondary)' }} />
Document Templates
</h2>
<button className="px-4 py-2 text-white rounded-lg hover:bg-green-700 transition-colors" style={{ backgroundColor: 'var(--secondary)', color: 'var(--secondary-foreground)' }}>
Upload Template
</button>
</div>
{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4" style={{ color: 'var(--muted-foreground)' }} />
<input
type="text"
placeholder="Search templates..."
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-2 focus:border-transparent"
style={{
borderColor: 'var(--border)',
backgroundColor: 'var(--background)',
color: 'var(--foreground)',
'--ring': 'var(--ring)'
}}
/>
</div>
</div>
<div className="flex-1 flex">
{/* Template Types */}
<div className="w-80 border-r p-4" style={{ borderColor: 'var(--border)', backgroundColor: 'var(--background)' }}>
<h3 className="font-semibold mb-4" style={{ color: 'var(--muted-foreground)' }}>Template Types</h3>
<div className="space-y-2">
{templateCategories.map((category, index) => {
const IconComponent = category.icon;
return (
<button
key={index}
className="w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card"
style={{ color: 'var(--card-foreground)' }}
>
<div className="flex items-center gap-3">
<IconComponent className="h-4 w-4" style={{ color: `var(${category.color})` }} />
<span className="font-medium">{category.name}</span>
</div>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>
{category.count}
</span>
</button>
);
})}
</div>
{/* Quick Actions */}
<div className="mt-8">
<h3 className="font-semibold mb-4" style={{ color: 'var(--muted-foreground)' }}>Quick Start</h3>
<div className="space-y-2">
<button className="w-full p-3 text-left rounded-lg transition-colors border-l-4" style={{ backgroundColor: 'var(--primary)/10', borderColor: 'var(--primary)', color: 'var(--primary)' }}>
<div className="font-medium">Blank Document</div>
<div className="text-xs">Start with empty document</div>
</button>
<button className="w-full p-3 text-left rounded-lg transition-colors border-l-4" style={{ backgroundColor: 'var(--destructive)/10', borderColor: 'var(--destructive)', color: 'var(--destructive)' }}>
<div className="font-medium">Blank Presentation</div>
<div className="text-xs">Create new slideshow</div>
</button>
<button className="w-full p-3 text-left rounded-lg transition-colors border-l-4" style={{ backgroundColor: 'var(--secondary)/10', borderColor: 'var(--secondary)', color: 'var(--secondary)' }}>
<div className="font-medium">Blank Spreadsheet</div>
<div className="text-xs">New data table</div>
</button>
</div>
</div>
</div>
{/* Templates Grid */}
<div className="flex-1 p-6" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{[...Array(9)].map((_, index) => (
<div key={index} className="border rounded-lg overflow-hidden transition-shadow" style={{ backgroundColor: 'var(--card)', color: 'var(--card-foreground)', borderColor: 'var(--border)' }}>
{/* Template Preview */}
<div className="h-40 flex items-center justify-center" style={{ background: 'linear-gradient(to bottom right, var(--primary)/20, var(--secondary)/30)' }}>
<FileText className="h-12 w-12" style={{ color: 'var(--primary)' }} />
</div>
<div className="p-4">
<h3 className="font-semibold mb-2">Business Proposal Template</h3>
<p className="text-sm mb-3" style={{ color: 'var(--muted-foreground)' }}>
Professional template for business proposals and project pitches.
</p>
<div className="flex items-center justify-between">
<span className="text-xs px-2 py-1 rounded" style={{ backgroundColor: 'var(--primary)', color: 'var(--primary-foreground)' }}>Document</span>
<button className="flex items-center gap-1 text-sm font-medium" style={{ color: 'var(--primary)' }}>
Use Template
<ArrowRight className="h-3 w-3" />
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
const renderNews = () => (
<div className="h-full flex flex-col" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
{/* Header */}
<div className="p-4 border-b" style={{ borderColor: 'var(--border)' }}>
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-bold flex items-center gap-2">
<Newspaper className="h-6 w-6" style={{ color: 'var(--primary)' }} />
News & Insights
</h2>
<div className="flex gap-2">
<button className="px-4 py-2 border rounded-lg transition-colors" style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}>
Customize Feed
</button>
<button className="px-4 py-2 text-white rounded-lg transition-colors" style={{ backgroundColor: 'var(--primary)', color: 'var(--primary-foreground)' }}>
Add Source
</button>
</div>
</div>
{/* Search & Filters */}
<div className="flex gap-4">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4" style={{ color: 'var(--muted-foreground)' }} />
<input
type="text"
placeholder="Search news and articles..."
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-2 focus:border-transparent"
style={{
borderColor: 'var(--border)',
backgroundColor: 'var(--background)',
color: 'var(--foreground)',
'--ring': 'var(--ring)'
}}
/>
</div>
<select className="px-4 py-2 border rounded-lg focus:ring-2 focus:border-transparent" style={{ borderColor: 'var(--border)', backgroundColor: 'var(--background)', color: 'var(--foreground)', '--ring': 'var(--ring)' }}>
<option>All Time</option>
<option>Today</option>
<option>This Week</option>
<option>This Month</option>
</select>
</div>
</div>
<div className="flex-1 flex">
{/* News Categories */}
<div className="w-80 border-r p-4" style={{ borderColor: 'var(--border)', backgroundColor: 'var(--background)' }}>
<h3 className="font-semibold mb-4" style={{ color: 'var(--muted-foreground)' }}>Categories</h3>
<div className="space-y-2">
{newsCategories.map((category, index) => (
<button
key={index}
className="w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card"
style={{ color: 'var(--card-foreground)' }}
>
<div className="flex items-center gap-3">
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: `var(${category.color})` }}
></div>
<span className="font-medium">{category.name}</span>
</div>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>
{category.count}
</span>
</button>
))}
</div>
{/* Trending Topics */}
<div className="mt-8">
<h3 className="font-semibold mb-4 flex items-center gap-2" style={{ color: 'var(--muted-foreground)' }}>
<TrendingUp className="h-4 w-4" style={{ color: 'var(--accent)' }} />
Trending
</h3>
<div className="space-y-2">
{['OpenAI GPT-5', 'Quantum Computing', 'Climate Tech', 'Space X'].map((topic, index) => (
<button key={index} className="w-full text-left p-2 hover:bg-card rounded-lg transition-colors" style={{ color: 'var(--card-foreground)' }}>
<span className="text-sm font-medium">#{topic}</span>
</button>
))}
</div>
</div>
</div>
{/* News Feed */}
<div className="flex-1 p-6" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
<div className="space-y-4">
{[...Array(6)].map((_, index) => (
<article key={index} className="border rounded-lg p-4 transition-shadow" style={{ backgroundColor: 'var(--card)', color: 'var(--card-foreground)', borderColor: 'var(--border)' }}>
<div className="flex items-start gap-4">
<div className="w-24 h-24 rounded-lg flex-shrink-0 flex items-center justify-center" style={{ background: 'linear-gradient(to bottom right, var(--primary)/20, var(--secondary)/30)' }}>
<Newspaper className="h-8 w-8" style={{ color: 'var(--primary)' }} />
</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<span className="text-xs px-2 py-1 rounded" style={{ backgroundColor: 'var(--primary)', color: 'var(--primary-foreground)' }}>AI & ML</span>
<span className="text-xs" style={{ color: 'var(--muted-foreground)' }}>TechCrunch</span>
<span className="text-xs" style={{ color: 'var(--muted-foreground)' }}></span>
<span className="text-xs flex items-center gap-1" style={{ color: 'var(--muted-foreground)' }}>
<Clock className="h-3 w-3" />
2 hours ago
</span>
</div>
<h3 className="font-semibold text-lg mb-2 hover:text-primary cursor-pointer">
OpenAI Announces GPT-5 with Revolutionary Capabilities
</h3>
<p className="text-sm mb-3" style={{ color: 'var(--muted-foreground)' }}>
The latest AI model shows unprecedented reasoning abilities and multimodal understanding,
setting new benchmarks across various tasks and potentially reshaping the AI landscape...
</p>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4 text-sm" style={{ color: 'var(--muted-foreground)' }}>
<button className="hover:text-primary transition-colors">Save</button>
<button className="hover:text-primary transition-colors">Share</button>
<button className="hover:text-primary transition-colors">Discuss</button>
</div>
<button className="flex items-center gap-1 text-sm font-medium" style={{ color: 'var(--primary)' }}>
Read Full Article
<ExternalLink className="h-3 w-3" />
</button>
</div>
</div>
</div>
</article>
))}
</div>
</div>
</div>
</div>
);
const renderMCPServers = () => (
<div className="h-full flex flex-col" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
{/* Header */}
<div className="p-4 border-b" style={{ borderColor: 'var(--border)' }}>
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-bold flex items-center gap-2">
<Server className="h-6 w-6" style={{ color: 'var(--chart-2)' }} />
MCP Servers
</h2>
<div className="flex gap-2">
<button className="px-4 py-2 border rounded-lg transition-colors" style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}>
Documentation
</button>
<button className="px-4 py-2 text-white rounded-lg transition-colors" style={{ backgroundColor: 'var(--chart-2)', color: 'var(--chart-2-foreground)' }}>
Create Server
</button>
</div>
</div>
{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4" style={{ color: 'var(--muted-foreground)' }} />
<input
type="text"
placeholder="Search MCP servers..."
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-2 focus:border-transparent"
style={{
borderColor: 'var(--border)',
backgroundColor: 'var(--background)',
color: 'var(--foreground)',
'--ring': 'var(--ring)'
}}
/>
</div>
</div>
<div className="flex-1 flex">
{/* Categories Sidebar */}
<div className="w-80 border-r p-4" style={{ borderColor: 'var(--border)', backgroundColor: 'var(--background)' }}>
<h3 className="font-semibold mb-4" style={{ color: 'var(--muted-foreground)' }}>Categories</h3>
<div className="space-y-2">
{[
{ name: "developer-tools", count: 1203, color: "--primary" },
{ name: "search", count: 609, color: "--secondary" },
{ name: "communication", count: 233, color: "--chart-1" },
{ name: "entertainment", count: 106, color: "--chart-2" },
{ name: "file-systems", count: 207, color: "--chart-3" },
{ name: "finance", count: 226, color: "--chart-4" },
{ name: "knowledge", count: 264, color: "--chart-5" },
{ name: "location", count: 71, color: "--primary" },
{ name: "other", count: 294, color: "--accent" }
].map((category, index) => (
<button
key={index}
onClick={() => setSelectedCategory(category.name)}
className={`w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card ${
selectedCategory === category.name ? 'bg-card shadow-sm border-l-4' : ''
}`}
style={{
borderLeftColor: selectedCategory === category.name ? `var(${category.color})` : 'transparent',
backgroundColor: selectedCategory === category.name ? 'var(--card)' : '',
color: 'var(--card-foreground)'
}}
>
<div className="flex items-center gap-3">
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: `var(${category.color})` }}
></div>
<span className="font-medium">{category.name}</span>
</div>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>
{category.count}
</span>
</button>
))}
</div>
{/* Quick Filters */}
<div className="mt-8">
<h3 className="font-semibold mb-4" style={{ color: 'var(--muted-foreground)' }}>Server Type</h3>
<div className="space-y-2">
<button className="w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card" style={{ color: 'var(--card-foreground)' }}>
<span className="font-medium">Hosted</span>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>24</span>
</button>
<button className="w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card" style={{ color: 'var(--card-foreground)' }}>
<span className="font-medium">Local</span>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>8</span>
</button>
</div>
</div>
</div>
{/* Content Area */}
<div className="flex-1 p-6 overflow-y-auto" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{mcpServers.map((server, index) => {
const IconComponent = server.icon;
return (
<div key={index} className="border rounded-lg p-4 transition-shadow" style={{ backgroundColor: 'var(--card)', color: 'var(--card-foreground)', borderColor: 'var(--border)' }}>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<IconComponent className="h-5 w-5" style={{ color: 'var(--chart-2)' }} />
<h3 className="font-semibold">{server.name}</h3>
</div>
<span className={`text-xs px-2 py-1 rounded ${
server.type === 'Hosted' ? 'bg-blue-100 text-blue-800' : 'bg-green-100 text-green-800'
}`}>
{server.type}
</span>
</div>
<p className="text-sm mb-3" style={{ color: 'var(--muted-foreground)' }}>
{server.description}
</p>
<div className="flex items-center justify-between">
<span className="text-xs px-2 py-1 rounded" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>
{server.category}
</span>
<div className="flex items-center gap-2">
<span className="text-xs" style={{ color: 'var(--muted-foreground)' }}>{server.usage} uses</span>
<button className="flex items-center gap-1 text-sm font-medium" style={{ color: 'var(--chart-2)' }}>
<ArrowRight className="h-3 w-3" />
Connect
</button>
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
);
const renderLLMTools = () => (
<div className="h-full flex flex-col" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
{/* Header */}
<div className="p-4 border-b" style={{ borderColor: 'var(--border)' }}>
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-bold flex items-center gap-2">
<Cpu className="h-6 w-6" style={{ color: 'var(--chart-3)' }} />
LLM Tools
</h2>
<div className="flex gap-2">
<button className="px-4 py-2 border rounded-lg transition-colors" style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}>
Documentation
</button>
<button className="px-4 py-2 text-white rounded-lg transition-colors" style={{ backgroundColor: 'var(--chart-3)', color: 'var(--chart-3-foreground)' }}>
Create Tool
</button>
</div>
</div>
{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4" style={{ color: 'var(--muted-foreground)' }} />
<input
type="text"
placeholder="Search LLM tools..."
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-2 focus:border-transparent"
style={{
borderColor: 'var(--border)',
backgroundColor: 'var(--background)',
color: 'var(--foreground)',
'--ring': 'var(--ring)'
}}
/>
</div>
</div>
<div className="flex-1 flex">
{/* Categories Sidebar */}
<div className="w-80 border-r p-4" style={{ borderColor: 'var(--border)', backgroundColor: 'var(--background)' }}>
<h3 className="font-semibold mb-4" style={{ color: 'var(--muted-foreground)' }}>Categories</h3>
<div className="space-y-2">
{[
{ name: "User Management", count: 12, color: "--primary" },
{ name: "Location Services", count: 8, color: "--accent" },
{ name: "E-commerce", count: 6, color: "--secondary" },
{ name: "Knowledge", count: 15, color: "--chart-2" },
{ name: "Development", count: 22, color: "--primary" },
{ name: "Analytics", count: 9, color: "--chart-1" }
].map((category, index) => (
<button
key={index}
onClick={() => setSelectedCategory(category.name)}
className={`w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card ${
selectedCategory === category.name ? 'bg-card shadow-sm border-l-4' : ''
}`}
style={{
borderLeftColor: selectedCategory === category.name ? `var(${category.color})` : 'transparent',
backgroundColor: selectedCategory === category.name ? 'var(--card)' : '',
color: 'var(--card-foreground)'
}}
>
<div className="flex items-center gap-3">
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: `var(${category.color})` }}
></div>
<span className="font-medium">{category.name}</span>
</div>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>
{category.count}
</span>
</button>
))}
</div>
{/* Featured Tools */}
<div className="mt-8">
<h3 className="font-semibold mb-4 flex items-center gap-2" style={{ color: 'var(--muted-foreground)' }}>
<Star className="h-4 w-4" style={{ color: 'var(--chart-4)' }} />
Most Used
</h3>
<div className="space-y-3">
{llmTools.slice(0, 3).map((tool, index) => (
<div key={index} className="p-3 rounded-lg shadow-sm border" style={{ backgroundColor: 'var(--card)', color: 'var(--card-foreground)', borderColor: 'var(--border)' }}>
<div className="flex items-center gap-2">
<div
className="w-3 h-3 rounded-full flex-shrink-0"
style={{ backgroundColor: `var(${tool.color})` }}
></div>
<h4 className="font-medium text-sm">{tool.name}</h4>
</div>
<p className="text-xs mt-1" style={{ color: 'var(--muted-foreground)' }}>{tool.description}</p>
</div>
))}
</div>
</div>
</div>
{/* Content Area */}
<div className="flex-1 p-6 overflow-y-auto" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{llmTools.map((tool, index) => {
const IconComponent = tool.icon;
return (
<div key={index} className="border rounded-lg p-4 transition-shadow" style={{ backgroundColor: 'var(--card)', color: 'var(--card-foreground)', borderColor: 'var(--border)' }}>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<IconComponent className="h-5 w-5" style={{ color: `var(${tool.color})` }} />
<h3 className="font-semibold">{tool.name}</h3>
</div>
<button className="p-1 hover:bg-gray-100 rounded" style={{ '--hover-bg': 'var(--muted)' }}>
<Copy className="h-4 w-4" style={{ color: 'var(--muted-foreground)' }} />
</button>
</div>
<p className="text-sm mb-3" style={{ color: 'var(--muted-foreground)' }}>
{tool.description}
</p>
<div className="flex items-center justify-between">
<span className="text-xs px-2 py-1 rounded" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>
{tool.category}
</span>
<button className="flex items-center gap-1 text-sm font-medium" style={{ color: 'var(--chart-3)' }}>
<Play className="h-3 w-3" />
Run Tool
</button>
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
);
return (
<div className="h-screen flex flex-col" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
{/* Tab Navigation */}
<div className="border-b" style={{ backgroundColor: 'var(--background)', borderColor: 'var(--border)' }}>
<div className="flex">
{[
{ id: 'prompts', label: 'Prompts', icon: Lightbulb, color: 'text-yellow-600' },
{ id: 'templates', label: 'Templates', icon: FileText, color: 'text-green-600' },
{ id: 'news', label: 'News', icon: Newspaper, color: 'text-blue-600' },
{ id: 'mcp', label: 'MCP Servers', icon: Server, color: 'text-purple-600' },
{ id: 'llm-tools', label: 'LLM Tools', icon: Cpu, color: 'text-indigo-600' }
].map((tab) => {
const IconComponent = tab.icon;
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`flex items-center gap-2 px-6 py-4 border-b-2 transition-colors font-medium ${
activeTab === tab.id
? `border-current ${tab.color} bg-card`
: 'border-transparent hover:bg-card'
}`}
style={{
color: activeTab === tab.id ? `var(${tab.color.split('-')[1]})` : 'var(--muted-foreground)',
'--current': activeTab === tab.id ? `var(${tab.color.split('-')[1]})` : ''
}}
>
<IconComponent className="h-4 w-4" />
{tab.label}
</button>
);
})}
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-hidden">
{activeTab === 'prompts' && renderPrompts()}
{activeTab === 'templates' && renderTemplates()}
{activeTab === 'news' && renderNews()}
{activeTab === 'mcp' && renderMCPServers()}
{activeTab === 'llm-tools' && renderLLMTools()}
</div>
</div>
);
}

View file

@ -1,36 +0,0 @@
import React from 'react';
import { Album } from "../data/albums"
interface AlbumArtworkProps {
album: Album
aspectRatio?: "portrait" | "square"
width?: number
height?: number
}
export function AlbumArtwork({
album,
aspectRatio = "portrait",
width,
height,
}: AlbumArtworkProps) {
return (
<div className="mr-4">
<div className="overflow-hidden rounded-md">
<img
src={album.cover}
alt={album.name}
className={`object-cover transition-all hover:scale-105 ${aspectRatio === "portrait" ? "aspect-[3/4]" : "aspect-square"}`}
style={{
width: width,
height: height,
}}
/>
</div>
<div className="mt-2 space-y-1 text-sm">
<h3 className="font-medium leading-none">{album.name}</h3>
<p className="text-xs text-gray-500">{album.artist}</p>
</div>
</div>
);
}

View file

@ -1,13 +0,0 @@
import React from 'react';
export function Menu() {
return (
<div className="flex justify-around py-2 bg-gray-100">
<button className="px-4 py-2 text-sm font-medium">Music</button>
<button className="px-4 py-2 text-sm font-medium">File</button>
<button className="px-4 py-2 text-sm font-medium">Edit</button>
<button className="px-4 py-2 text-sm font-medium">View</button>
<button className="px-4 py-2 text-sm font-medium">Account</button>
</div>
);
}

View file

@ -1,17 +0,0 @@
import React from 'react';
export function PodcastEmptyPlaceholder() {
return (
<div className="flex items-center justify-center h-full p-8">
<div className="text-center">
<h3 className="text-xl font-bold">No episodes added</h3>
<p className="mb-4 text-sm text-gray-500">
You have not added any podcasts. Add one below.
</p>
<button className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Add Podcast
</button>
</div>
</div>
);
}

View file

@ -1,41 +0,0 @@
import React from 'react';
import { Playlist } from "../data/playlists"
interface SidebarProps {
playlists: Playlist[]
}
export function Sidebar({ playlists }: SidebarProps) {
return (
<div className="p-3">
<div className="mb-5">
<h2 className="text-lg font-bold mb-2">Discover</h2>
<div className="space-y-1 ml-2">
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Listen Now</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Browse</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Radio</button>
</div>
</div>
<div className="mb-5">
<h2 className="text-lg font-bold mb-2">Library</h2>
<div className="space-y-1 ml-2">
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Playlists</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Songs</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Made for You</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Artists</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Albums</button>
</div>
</div>
<div>
<h2 className="text-lg font-bold mb-2">Playlists</h2>
<div className="max-h-72 overflow-y-auto">
{playlists?.map((playlist, i) => (
<button key={i} className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">
{playlist}
</button>
))}
</div>
</div>
</div>
);
}

View file

@ -1,213 +0,0 @@
import { ChevronDown, ChevronRight, Search, Star, Grid, List } from 'lucide-react'
interface Prompt {
id: string
name: string
category: string
description: string
icon: string
featured?: boolean
}
const prompts: Prompt[] = [
{
id: "1",
name: "SOP Analyzer",
category: "Education",
description: "Analyzes statements of purpose for academic applications",
icon: "📝",
featured: true
},
{
id: "2",
name: "Break This GPT",
category: "Security",
description: "Tests GPT vulnerabilities and security",
icon: "🔓",
featured: true
},
// Add all your prompts here...
{
id: "100",
name: "Coinflipper Game",
category: "Games",
description: "Simple coin flipping game",
icon: "🪙"
}
]
const categories = Array.from(new Set(prompts.map(p => p.category)))
const featuredPrompts = prompts.filter(p => p.featured)
export default function PromptsPage() {
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
<div className="container mx-auto px-4 py-3 flex items-center justify-between">
<div className="flex items-center space-x-6">
<h1 className="text-xl font-bold">Prompt Store</h1>
<nav className="hidden md:flex space-x-6">
<button className="text-sm font-medium">Discover</button>
<button className="text-sm font-medium">Categories</button>
<button className="text-sm font-medium">Top Charts</button>
<button className="text-sm font-medium">Collections</button>
</nav>
</div>
<div className="flex items-center space-x-4">
<button className="p-2 rounded-full hover:bg-gray-100">
<Search className="h-4 w-4" />
</button>
<button className="px-4 py-2 text-sm font-medium rounded-full bg-black text-white">
Sign In
</button>
</div>
</div>
</header>
{/* Main Content */}
<main className="container mx-auto px-4 py-6">
{/* Hero Section */}
<section className="mb-12">
<div className="bg-gradient-to-r from-blue-500 to-purple-600 rounded-2xl p-8 text-white">
<h2 className="text-3xl font-bold mb-2">Discover Amazing GPTs</h2>
<p className="text-lg mb-6">Browse hundreds of specialized AI assistants</p>
<div className="relative max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
<input
type="text"
placeholder="Search prompts..."
className="w-full pl-10 pr-4 py-2 rounded-full text-gray-900 focus:outline-none"
/>
</div>
</div>
</section>
{/* Featured Section */}
<section className="mb-12">
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold">Featured Prompts</h2>
<button className="text-blue-600 text-sm font-medium flex items-center">
See All <ChevronRight className="h-4 w-4 ml-1" />
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{featuredPrompts.map(prompt => (
<div key={prompt.id} className="bg-white rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-shadow">
<div className="p-5">
<div className="flex items-center mb-4">
<span className="text-3xl mr-3">{prompt.icon}</span>
<div>
<h3 className="font-semibold">{prompt.name}</h3>
<p className="text-xs text-gray-500">{prompt.category}</p>
</div>
</div>
<p className="text-sm text-gray-600 mb-4 line-clamp-2">{prompt.description}</p>
<div className="flex justify-between items-center">
<div className="flex items-center">
<Star className="h-4 w-4 text-yellow-500 fill-yellow-500" />
<span className="text-xs ml-1">Featured</span>
</div>
<button className="text-xs font-medium text-blue-600 hover:text-blue-800">
Get Prompt
</button>
</div>
</div>
</div>
))}
</div>
</section>
{/* Categories Section */}
<section>
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold">Browse by Category</h2>
<div className="flex items-center space-x-2">
<button className="p-2 rounded-md hover:bg-gray-100">
<Grid className="h-5 w-5" />
</button>
<button className="p-2 rounded-md hover:bg-gray-100">
<List className="h-5 w-5" />
</button>
</div>
</div>
<div className="space-y-8">
{categories.map(category => {
const categoryPrompts = prompts.filter(p => p.category === category)
return (
<div key={category}>
<div className="flex items-center justify-between mb-4">
<h3 className="text-xl font-semibold">{category}</h3>
<button className="text-sm text-blue-600 font-medium flex items-center">
See All <ChevronRight className="h-4 w-4 ml-1" />
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{categoryPrompts.slice(0, 4).map(prompt => (
<div key={prompt.id} className="bg-white rounded-lg border overflow-hidden hover:shadow-sm transition-shadow">
<div className="p-4">
<div className="flex items-center mb-3">
<span className="text-2xl mr-2">{prompt.icon}</span>
<h4 className="font-medium">{prompt.name}</h4>
</div>
<p className="text-sm text-gray-600 mb-4 line-clamp-2">{prompt.description}</p>
<button className="w-full py-2 text-sm font-medium rounded-md bg-gray-100 hover:bg-gray-200">
View Details
</button>
</div>
</div>
))}
</div>
</div>
)
})}
</div>
</section>
</main>
{/* Footer */}
<footer className="bg-gray-100 border-t mt-12">
<div className="container mx-auto px-4 py-8">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
<div>
<h4 className="font-semibold mb-4">The Prompt Store</h4>
<ul className="space-y-2 text-sm">
<li><a href="#" className="hover:text-blue-600">About Us</a></li>
<li><a href="#" className="hover:text-blue-600">Careers</a></li>
<li><a href="#" className="hover:text-blue-600">News</a></li>
</ul>
</div>
<div>
<h4 className="font-semibold mb-4">For Developers</h4>
<ul className="space-y-2 text-sm">
<li><a href="#" className="hover:text-blue-600">Submit a Prompt</a></li>
<li><a href="#" className="hover:text-blue-600">Documentation</a></li>
<li><a href="#" className="hover:text-blue-600">API</a></li>
</ul>
</div>
<div>
<h4 className="font-semibold mb-4">Support</h4>
<ul className="space-y-2 text-sm">
<li><a href="#" className="hover:text-blue-600">Help Center</a></li>
<li><a href="#" className="hover:text-blue-600">Community</a></li>
<li><a href="#" className="hover:text-blue-600">Contact Us</a></li>
</ul>
</div>
<div>
<h4 className="font-semibold mb-4">Legal</h4>
<ul className="space-y-2 text-sm">
<li><a href="#" className="hover:text-blue-600">Terms of Service</a></li>
<li><a href="#" className="hover:text-blue-600">Privacy Policy</a></li>
<li><a href="#" className="hover:text-blue-600">Cookie Policy</a></li>
</ul>
</div>
</div>
<div className="border-t mt-8 pt-8 text-sm text-gray-500">
© {new Date().getFullYear()} Prompt Store. All rights reserved.
</div>
</div>
</footer>
</div>
)
}

View file

@ -19,7 +19,7 @@ const themes: Theme[] = [
{ name: 'jazzage', label: 'Jazzage', cssFile: '/themes/jazzage.css' },
{ name: 'mellowgold', label: 'Mellowgold', cssFile: '/themes/mellowgold.css' },
{ name: 'midcenturymod', label: 'Midcenturymod', cssFile: '/themes/midcenturymod.css' },
{ name: 'oldhollywood', label: 'Oldhollywood', cssFile: '/themes/oldhollywood.css' },
{ name: 'orange', label: 'Orange', cssFile: '/themes/orange.css' },
{ name: 'polaroidmemories', label: 'Polaroidmemories', cssFile: '/themes/polaroidmemories.css' },
{ name: 'retrowave', label: 'Retrowave', cssFile: '/themes/retrowave.css' },
{ name: 'saturdaycartoons', label: 'Saturdaycartoons', cssFile: '/themes/saturdaycartoons.css' },
@ -27,7 +27,8 @@ const themes: Theme[] = [
{ name: 'typewriter', label: 'Typewriter', cssFile: '/themes/typewriter.css' },
{ name: 'vapordream', label: 'Vapordream', cssFile: '/themes/vapordream.css' },
{ name: 'xeroxui', label: 'Xeroxui', cssFile: '/themes/xeroxui.css' },
{ name: 'y2kglow', label: 'Y2kglow', cssFile: '/themes/y2kglow.css' }
{ name: 'y2kglow', label: 'Y2kglow', cssFile: '/themes/y2kglow.css' },
{ name: 'xtreegold', label: 'XTreeGold', cssFile: '/themes/xtreegold.css' }
]

View file

@ -1,28 +1,66 @@
:root {
/* 3DBevel Theme */
--background: 0 0% 80%;
--foreground: 0 0% 10%;
--card: 0 0% 75%;
--card-foreground: 0 0% 10%;
--popover: 0 0% 80%;
--popover-foreground: 0 0% 10%;
--primary: 210 80% 40%;
--primary-foreground: 0 0% 80%;
--secondary: 0 0% 70%;
--secondary-foreground: 0 0% 10%;
--muted: 0 0% 65%;
--muted-foreground: 0 0% 30%;
--accent: 30 80% 40%;
--accent-foreground: 0 0% 80%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 70%;
--input: 0 0% 70%;
--ring: 210 80% 40%;
--radius: 0.5rem;
--chart-1: 210 80% 40%;
--chart-2: 30 80% 40%;
--chart-3: 120 80% 40%;
--chart-4: 300 80% 40%;
--chart-5: 240 80% 40%;
body, .card, .popover, .input, .button, .menu, .dialog {
font-family: 'IBM Plex Mono', 'Courier New', monospace !important;
background: #c0c0c0 !important;
color: #000 !important;
border-radius: 0 !important;
box-shadow: none !important;
}
.card, .popover, .menu, .dialog {
border: 2px solid #fff !important;
border-bottom: 2px solid #404040 !important;
border-right: 2px solid #404040 !important;
padding: 8px !important;
background: #e0e0e0 !important;
}
.button, button, input[type="button"], input[type="submit"] {
background: #e0e0e0 !important;
color: #000 !important;
border: 2px solid #fff !important;
border-bottom: 2px solid #404040 !important;
border-right: 2px solid #404040 !important;
padding: 4px 12px !important;
font-weight: bold !important;
box-shadow: none !important;
outline: none !important;
}
input, textarea, select {
background: #fff !important;
color: #000 !important;
border: 2px solid #fff !important;
border-bottom: 2px solid #404040 !important;
border-right: 2px solid #404040 !important;
font-family: inherit !important;
box-shadow: none !important;
}
.menu {
background: #d0d0d0 !important;
border: 2px solid #fff !important;
border-bottom: 2px solid #404040 !important;
border-right: 2px solid #404040 !important;
}
::-webkit-scrollbar {
width: 16px !important;
background: #c0c0c0 !important;
}
::-webkit-scrollbar-thumb {
background: #404040 !important;
border: 2px solid #fff !important;
border-bottom: 2px solid #404040 !important;
border-right: 2px solid #404040 !important;
}
a {
color: #0000aa !important;
text-decoration: underline !important;
}
hr {
border: none !important;
border-top: 2px solid #404040 !important;
margin: 8px 0 !important;
}

View file

@ -1,28 +0,0 @@
:root {
/* OldHollywood Theme */
--background: 30 20% 10%;
--foreground: 30 30% 85%;
--card: 30 20% 15%;
--card-foreground: 30 30% 85%;
--popover: 30 20% 10%;
--popover-foreground: 30 30% 85%;
--primary: 10 70% 50%;
--primary-foreground: 30 30% 85%;
--secondary: 30 20% 20%;
--secondary-foreground: 30 30% 85%;
--muted: 30 20% 25%;
--muted-foreground: 30 30% 60%;
--accent: 40 70% 50%;
--accent-foreground: 30 30% 85%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 30 20% 20%;
--input: 30 20% 20%;
--ring: 10 70% 50%;
--radius: 0.5rem;
--chart-1: 10 70% 50%;
--chart-2: 40 70% 50%;
--chart-3: 350 70% 50%;
--chart-4: 200 70% 50%;
--chart-5: 280 70% 50%;
}

27
public/themes/orange.css Normal file
View file

@ -0,0 +1,27 @@
:root {
--background: 0 0% 100%; /* White */
--foreground: 0 0% 13%; /* #212121 - near black */
--card: 0 0% 98%; /* #faf9f8 - light gray */
--card-foreground: 0 0% 13%; /* #212121 */
--popover: 0 0% 100%; /* White */
--popover-foreground: 0 0% 13%; /* #212121 */
--primary: 24 90% 54%; /* #d83b01 - Office orange */
--primary-foreground: 0 0% 100%; /* White */
--secondary: 210 36% 96%; /* #f3f2f1 - light blue-gray */
--secondary-foreground: 0 0% 13%; /* #212121 */
--muted: 0 0% 90%; /* #e1dfdd - muted gray */
--muted-foreground: 0 0% 40%; /* #666666 */
--accent: 207 90% 54%; /* #0078d4 - Office blue */
--accent-foreground: 0 0% 100%; /* White */
--destructive: 0 85% 60%; /* #e81123 - Office red */
--destructive-foreground: 0 0% 100%; /* White */
--border: 0 0% 85%; /* #d2d0ce - light border */
--input: 0 0% 100%; /* White */
--ring: 207 90% 54%; /* #0078d4 */
--radius: 0.25rem; /* Slightly less rounded */
--chart-1: 24 90% 54%; /* Office orange */
--chart-2: 207 90% 54%; /* Office blue */
--chart-3: 120 60% 40%; /* Office green */
--chart-4: 340 82% 52%; /* Office magenta */
--chart-5: 44 100% 50%; /* Office yellow */
}

View file

@ -1,28 +1,71 @@
:root {
/* XeroxUI Theme */
--background: 0 0% 90%;
--foreground: 240 10% 10%;
--card: 0 0% 85%;
--card-foreground: 240 10% 10%;
--popover: 0 0% 90%;
--popover-foreground: 240 10% 10%;
--primary: 240 80% 40%;
--primary-foreground: 0 0% 90%;
--secondary: 0 0% 80%;
--secondary-foreground: 240 10% 10%;
--muted: 0 0% 75%;
--muted-foreground: 240 10% 30%;
--accent: 120 80% 40%;
--accent-foreground: 0 0% 90%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 80%;
--input: 0 0% 80%;
--ring: 240 80% 40%;
--radius: 0.5rem;
--chart-1: 240 80% 40%;
--chart-2: 120 80% 40%;
--chart-3: 60 80% 40%;
--chart-4: 0 80% 40%;
--chart-5: 300 80% 40%;
/* Windows 3.1 White & Blue Theme */
--background: 0 0% 100%; /* Pure white */
--foreground: 0 0% 0%; /* Black text */
--card: 0 0% 98%; /* Slightly off-white for cards */
--card-foreground: 0 0% 0%; /* Black text */
--popover: 0 0% 100%; /* White */
--popover-foreground: 0 0% 0%; /* Black */
--primary: 240 100% 27%; /* Windows blue */
--primary-foreground: 0 0% 100%; /* White text on blue */
--secondary: 0 0% 90%; /* Light gray for secondary */
--secondary-foreground: 0 0% 0%; /* Black text */
--muted: 0 0% 85%; /* Muted gray */
--muted-foreground: 240 10% 40%; /* Muted blue-gray */
--accent: 60 100% 50%; /* Classic yellow accent */
--accent-foreground: 240 100% 27%; /* Blue */
--destructive: 0 100% 50%; /* Red for destructive */
--destructive-foreground: 0 0% 100%; /* White */
--border: 240 100% 27%; /* Blue borders */
--input: 0 0% 100%; /* White input */
--ring: 240 100% 27%; /* Blue ring/focus */
--radius: 0.125rem; /* Small radius, almost square */
--chart-1: 240 100% 27%; /* Blue */
--chart-2: 0 0% 60%; /* Gray */
--chart-3: 60 100% 50%; /* Yellow */
--chart-4: 0 100% 50%; /* Red */
--chart-5: 120 100% 25%; /* Green */
--border-light: 0 0% 100%; /* White for top/left border */
--border-dark: 240 100% 20%; /* Dark blue for bottom/right border */
}
/* Windows 3.11 style border */
.win311-border {
border-top: 2px solid hsl(var(--border-light));
border-left: 2px solid hsl(var(--border-light));
border-bottom: 2px solid hsl(var(--border-dark));
border-right: 2px solid hsl(var(--border-dark));
background: hsl(var(--background));
}
/* Titles */
.win311-title {
color: hsl(var(--primary));
border-bottom: 2px solid hsl(var(--primary));
font-weight: bold;
padding: 0.25em 0.5em;
background: hsl(var(--background));
}
/* General text */
body, .filemanager, .filemanager * {
color: hsl(var(--foreground));
background: hsl(var(--background));
}
button, .win311-button {
font-family: inherit;
font-size: 1em;
padding: 0.25em 1.5em;
background: #c0c0c0; /* classic light gray */
color: #000;
border-top: 2px solid #fff; /* light bevel */
border-left: 2px solid #fff; /* light bevel */
border-bottom: 2px solid #808080;/* dark bevel */
border-right: 2px solid #808080; /* dark bevel */
border-radius: 0;
box-shadow: inset 1px 1px 0 #fff, inset -1px -1px 0 #808080 !important;
outline: none !important;
cursor: pointer !important;
transition: none !important;
}

228
public/themes/xtreegold.css Normal file
View file

@ -0,0 +1,228 @@
:root {
/* XTree Gold DOS File Manager Theme - Authentic 1980s Interface */
/* Core XTree Gold Palette - Exact Match */
--background: 240 100% 16%; /* Classic XTree blue background */
--foreground: 60 100% 88%; /* Bright yellow text */
/* Card Elements - File Panels */
--card: 240 100% 16%; /* Same blue as main background */
--card-foreground: 60 100% 88%; /* Bright yellow panel text */
/* Popover Elements - Context Menus */
--popover: 240 100% 12%; /* Slightly darker blue for menus */
--popover-foreground: 60 100% 90%; /* Bright yellow menu text */
/* Primary - XTree Gold Highlight (Cyan Selection) */
--primary: 180 100% 70%; /* Bright cyan for selections */
--primary-foreground: 240 100% 10%; /* Dark blue text on cyan */
/* Secondary - Directory Highlights */
--secondary: 180 100% 50%; /* Pure cyan for directories */
--secondary-foreground: 240 100% 10%; /* Dark blue on cyan */
/* Muted - Status Areas */
--muted: 240 100% 14%; /* Slightly darker blue */
--muted-foreground: 60 80% 75%; /* Dimmed yellow */
/* Accent - Function Keys & Highlights */
--accent: 60 100% 50%; /* Pure yellow for F-keys */
--accent-foreground: 240 100% 10%; /* Dark blue on yellow */
/* Destructive - Delete/Error */
--destructive: 0 100% 60%; /* Bright red for warnings */
--destructive-foreground: 60 90% 95%; /* Light yellow on red */
/* Interactive Elements */
--border: 60 100% 70%; /* Yellow border lines */
--input: 240 100% 14%; /* Dark blue input fields */
--ring: 180 100% 70%; /* Cyan focus ring */
/* Border Radius - Sharp DOS aesthetic */
--radius: 0rem; /* No rounding - pure DOS */
/* Chart Colors - Authentic DOS 16-color palette */
--chart-1: 180 100% 70%; /* Bright cyan */
--chart-2: 60 100% 50%; /* Yellow */
--chart-3: 120 100% 50%; /* Green */
--chart-4: 300 100% 50%; /* Magenta */
--chart-5: 0 100% 60%; /* Red */
/* Authentic XTree Gold Colors */
--xtree-blue: 240 100% 16%; /* Main background blue */
--xtree-yellow: 60 100% 88%; /* Text yellow */
--xtree-cyan: 180 100% 70%; /* Selection cyan */
--xtree-white: 0 0% 100%; /* Pure white */
--xtree-green: 120 100% 50%; /* DOS green */
--xtree-magenta: 300 100% 50%; /* DOS magenta */
--xtree-red: 0 100% 60%; /* DOS red */
/* File Type Colors - Authentic XTree */
--executable-color: 0 0% 100%; /* White for executables */
--directory-color: 180 100% 70%; /* Cyan for directories */
--archive-color: 300 100% 50%; /* Magenta for archives */
--text-color: 60 100% 88%; /* Yellow for text */
--system-color: 0 100% 60%; /* Red for system files */
/* Menu Bar Colors */
--menu-bar: 240 100% 8%; /* Dark blue menu bar */
--menu-text: 60 100% 88%; /* Yellow menu text */
--menu-highlight: 180 100% 50%; /* Cyan menu highlight */
}
/* Authentic XTree Gold Enhancement Classes */
.xtree-main-panel {
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
font-family: 'Perfect DOS VGA 437', 'Courier New', monospace;
font-size: 16px;
line-height: 1;
border: none;
}
.xtree-menu-bar {
background: hsl(var(--menu-bar));
color: hsl(var(--menu-text));
padding: 0;
height: 20px;
display: flex;
align-items: center;
font-weight: normal;
}
.xtree-menu-item {
padding: 0 8px;
color: hsl(var(--xtree-yellow));
background: transparent;
}
.xtree-menu-item:hover,
.xtree-menu-item.active {
background: hsl(var(--xtree-cyan));
color: hsl(240 100% 10%);
}
.xtree-dual-pane {
display: flex;
height: calc(100vh - 60px);
}
.xtree-left-pane,
.xtree-right-pane {
flex: 1;
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
padding: 0;
margin: 0;
}
.xtree-directory-tree {
color: hsl(var(--directory-color));
background: hsl(var(--xtree-blue));
padding: 4px;
}
.xtree-file-list {
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
font-family: 'Perfect DOS VGA 437', 'Courier New', monospace;
font-size: 16px;
line-height: 20px;
padding: 4px;
}
.xtree-file-selected {
background: hsl(var(--xtree-cyan));
color: hsl(240 100% 10%);
}
.xtree-directory {
color: hsl(var(--directory-color));
}
.xtree-executable {
color: hsl(var(--executable-color));
}
.xtree-archive {
color: hsl(var(--archive-color));
}
.xtree-text-file {
color: hsl(var(--text-color));
}
.xtree-system-file {
color: hsl(var(--system-color));
}
.xtree-status-line {
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
height: 20px;
padding: 0 8px;
display: flex;
align-items: center;
font-size: 16px;
}
.xtree-function-bar {
background: hsl(var(--menu-bar));
color: hsl(var(--xtree-yellow));
height: 20px;
display: flex;
padding: 0;
font-size: 14px;
}
.xtree-function-key {
padding: 0 4px;
color: hsl(var(--xtree-yellow));
border-right: 1px solid hsl(var(--xtree-yellow));
}
.xtree-function-key:last-child {
border-right: none;
}
.xtree-path-bar {
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
padding: 2px 8px;
border-bottom: 1px solid hsl(var(--xtree-yellow));
}
.xtree-disk-info {
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
padding: 4px 8px;
text-align: right;
font-size: 14px;
}
/* Authentic DOS Box Drawing Characters */
.xtree-box-char {
font-family: 'Perfect DOS VGA 437', 'Courier New', monospace;
line-height: 1;
letter-spacing: 0;
}
/* Classic Text Mode Cursor */
.xtree-cursor {
background: hsl(var(--xtree-yellow));
color: hsl(var(--xtree-blue));
animation: blink 1s infinite;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
/* Authentic DOS Window Styling */
.xtree-window {
border: 2px outset hsl(var(--xtree-blue));
background: hsl(var(--xtree-blue));
box-shadow: none;
border-radius: 0;
}