anycoder-6a0aba2e / index.html
b08x's picture
Upload folder using huggingface_hub
8467f52 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Syncopated Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<script>
tailwind.config = {
theme: {
extend: {
colors: {
rustic: {
900: '#1c1917',
800: '#292524',
700: '#44403c',
600: '#57534e',
500: '#78716c',
},
rust: {
DEFAULT: '#c2410c',
light: '#ea580c',
dim: 'rgba(194, 65, 12, 0.2)',
}
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
}
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');
body {
background-color: #1c1917;
color: #e7e5e4;
font-family: 'Inter', sans-serif;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #1c1917;
}
::-webkit-scrollbar-thumb {
background: #44403c;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #c2410c;
}
/* Chatbot Popup Animations */
.chatbot-toggle {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.chatbot-panel {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform-origin: bottom right;
}
.chatbot-panel.closed {
opacity: 0;
transform: scale(0.95) translateY(20px);
pointer-events: none;
}
/* Link Cards Hover Effects */
.link-card {
transition: all 0.2s ease;
border-left: 3px solid transparent;
}
.link-card:hover {
background-color: #292524;
border-left: 3px solid #c2410c;
transform: translateX(4px);
}
input:focus,
select:focus {
outline: none;
border-color: #c2410c;
box-shadow: 0 0 0 2px rgba(194, 65, 12, 0.2);
}
.nav-item.active {
background-color: rgba(194, 65, 12, 0.1);
color: #c2410c;
border-right: 3px solid #c2410c;
}
.iframe-container {
background: #000;
border-radius: 0.5rem;
overflow: hidden;
}
/* Dashboard Split Pane */
.dashboard-split {
display: flex;
flex-direction: column;
height: calc(100vh - 4rem - 3rem);
gap: 1rem;
}
.top-pane {
flex: 1;
min-height: 0;
overflow: hidden;
}
.bottom-pane {
flex: 0 0 280px;
min-height: 280px;
display: flex;
flex-direction: column;
}
/* Shortcuts Grid */
.shortcuts-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 0.75rem;
}
.shortcut-item {
background: #1c1917;
border: 1px solid #44403c;
border-radius: 0.5rem;
padding: 1rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
cursor: pointer;
transition: all 0.2s ease;
min-height: 90px;
}
.shortcut-item:hover {
background: #292524;
border-color: #c2410c;
transform: translateY(-2px);
}
.shortcut-item i {
font-size: 1.5rem;
}
.shortcut-item span {
font-size: 0.75rem;
text-align: center;
color: #a8a29e;
}
/* Pane resize handle */
.resize-handle {
height: 6px;
background: #44403c;
border-radius: 3px;
cursor: ns-resize;
margin: 0.25rem 0;
position: relative;
}
.resize-handle:hover {
background: #c2410c;
}
/* Pane header */
.pane-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0;
margin-bottom: 0.5rem;
}
.pane-title {
font-size: 0.875rem;
font-weight: 600;
color: #d6d3d1;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Modal backdrop */
.modal-backdrop {
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
}
/* Quick action buttons */
.quick-action-btn {
transition: all 0.2s ease;
}
.quick-action-btn:hover {
background: #c2410c;
transform: scale(1.05);
}
/* Activity feed styling */
.activity-item {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.5rem 0;
border-bottom: 1px solid #44403c;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-top: 6px;
}
</style>
</head>
<body class="h-screen flex flex-col overflow-hidden">
<!-- Header -->
<header class="h-16 bg-rustic-800 border-b border-rustic-700 flex items-center justify-between px-6 shrink-0 z-20">
<div class="flex items-center space-x-4">
<div class="w-8 h-8 rounded bg-rust flex items-center justify-center shadow-lg shadow-rust/20">
<i class="fas fa-cube text-white text-sm"></i>
</div>
<h1 class="text-xl font-semibold tracking-wide text-gray-100">SYNCOPATED</h1>
</div>
<div class="flex-1 max-w-2xl mx-8 hidden md:block">
<div class="flex space-x-2">
<form id="meta-search-form" class="relative flex-1">
<input
type="text"
id="search-input"
name="q"
placeholder="Search Global..."
class="w-full bg-rustic-900 border border-rustic-700 rounded-md py-1.5 px-4 pl-9 text-sm text-gray-300 focus:outline-none focus:border-rust transition"
/>
<button type="submit" class="absolute left-3 top-2 text-rustic-500">
<i class="fas fa-globe text-xs"></i>
</button>
</form>
<form id="rubygems-search-form" class="relative w-64 hidden lg:block">
<input
type="text"
id="rubygems-search-input"
name="q"
placeholder="RubyGems..."
class="w-full bg-rustic-900 border border-rustic-700 rounded-md py-1.5 px-4 pl-9 text-sm text-gray-300 focus:outline-none focus:border-rust transition"
/>
<button type="submit" class="absolute left-3 top-2 text-rustic-500">
<i class="fas fa-gem text-xs text-rust-light"></i>
</button>
</form>
</div>
</div>
<div class="flex items-center space-x-4">
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="text-xs text-rustic-500 hover:text-rust transition">Built with anycoder</a>
<button class="text-gray-400 hover:text-rust-light transition">
<i class="fas fa-bell"></i>
</button>
<div
class="w-8 h-8 rounded-full bg-rustic-700 flex items-center justify-center cursor-pointer border border-rustic-600">
<i class="fas fa-user text-gray-400 text-xs"></i>
</div>
</div>
</header>
<!-- Main Layout -->
<div class="flex flex-1 overflow-hidden">
<!-- Left Column: Link Modals (Prominently Featured) -->
<aside class="w-80 bg-rustic-900 border-r border-rustic-700 flex flex-col overflow-y-auto shrink-0 hidden md:flex">
<div class="p-4">
<h2 class="text-xs font-bold text-rustic-500 uppercase tracking-wider mb-4 pl-2">Launcher</h2>
<!-- Dashboard View (Link Categories) -->
<div id="dashboard-links-view" class="">
<!-- Linksspace-y-6 will be injected here via JS -->
</div>
</div>
<!-- Secondary Nav for other tabs -->
<div class="mt-auto p-4 border-t border-rustic-700">
<nav class="space-y-1">
<button class="nav-item w-full text-left px-3 py-2 rounded-md text-sm font-medium flex items-center space-x-3 text-gray-400 hover:bg-rustic-800" data-tab="documents">
<i class="fas fa-file-alt w-4"></i>
<span>Documents</span>
</button>
<button class="nav-item w-full text-left px-3 py-2 rounded-md text-sm font-medium flex items-center space-x-3 text-gray-400 hover:bg-rustic-800" data-tab="generate">
<i class="fas fa-code w-4"></i>
<span>Generate</span>
</button>
<button class="nav-item w-full text-left px-3 py-2 rounded-md text-sm font-medium flex items-center space-x-3 text-gray-400 hover:bg-rustic-800" data-tab="settings">
<i class="fas fa-cog w-4"></i>
<span>Settings</span>
</button>
</nav>
</div>
</aside>
<!-- Right Column: Main Content Area -->
<main class="flex-1 bg-rustic-900 overflow-y-auto relative p-6" id="main-content-area">
<!-- Tab Content: Documents -->
<div id="documents" class="tab-content hidden">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="bg-rustic-800 rounded-xl p-1 border border-rustic-700 shadow-lg">
<div
class="bg-rustic-900 rounded-t-lg px-4 py-3 border-b border-rustic-700 flex justify-between items-center">
<span class="text-sm font-medium text-gray-300">Document Management</span>
<i class="fas fa-external-link-alt text-xs text-gray-500"></i>
</div>
<div class="h-64 iframe-container">
<iframe src="https://docs.example.com/browse" class="w-full h-full" allowfullscreen></iframe>
</div>
</div>
<div class="bg-rustic-800 rounded-xl p-1 border border-rustic-700 shadow-lg">
<div
class="bg-rustic-900 rounded-t-lg px-4 py-3 border-b border-rustic-700 flex justify-between items-center">
<span class="text-sm font-medium text-gray-300">Knowledge Base</span>
<i class="fas fa-external-link-alt text-xs text-gray-500"></i>
</div>
<div class="h-64 iframe-container">
<iframe src="https://wiki.example.com/home" class="w-full h-full" allowfullscreen></iframe>
</div>
</div>
</div>
</div>
<!-- Tab Content: Generate -->
<div id="generate" class="tab-content hidden">
<div class="grid grid-cols-1 gap-6">
<div class="bg-rustic-800 rounded-xl p-1 border border-rustic-700 shadow-lg">
<div
class="bg-rustic-900 rounded-t-lg px-4 py-3 border-b border-rustic-700 flex justify-between items-center">
<span class="text-sm font-medium text-gray-300">Code Generator</span>
<i class="fas fa-robot text-rust"></i>
</div>
<div class="h-96 iframe-container">
<iframe src="https://directory.example.com" class="w-full h-full" allowfullscreen></iframe>
</div>
</div>
</div>
</div>
<!-- Tab Content: Settings -->
<div id="settings" class="tab-content hidden">
<div class="max-w-2xl">
<div class="bg-rustic-800 rounded-xl p-6 border border-rustic-700">
<h3 class="text-lg font-medium text-white mb-6">Data Management</h3>
<div class="flex items-center space-x-4">
<button id="exportLinksBtn" class="bg-rust hover:bg-rust-light text-white font-medium py-2 px-4 rounded-lg flex items-center space-x-2 transition shadow-lg shadow-rust/20">
<i class="fas fa-file-export"></i>
<span>Export Config</span>
</button>
<button id="importLinksBtn" class="bg-rustic-700 hover:bg-rustic-600 text-white font-medium py-2 px-4 rounded-lg flex items-center space-x-2 transition">
<i class="fas fa-file-import"></i>
<span>Import Config</span>
</button>
<input type="file" id="importFileInput" class="hidden" accept=".json" />
</div>
<p class="text-sm text-rustic-500 mt-4">
Manage your sidebar link configuration here.
</p>
</div>
</div>
</div>
<!-- Default View: Dashboard with Split Panes -->
<div id="dashboard" class="tab-content block h-full">
<!-- Split Dashboard Layout -->
<div class="dashboard-split">
<!-- Top Pane: Welcome & Activity -->
<div class="top-pane bg-rustic-800 rounded-xl border border-rustic-700 p-4 overflow-y-auto">
<div class="pane-header">
<span class="pane-title">Dashboard Overview</span>
<div class="flex items-center space-x-2">
<span class="text-xs text-rustic-500" id="currentDate"></span>
<div class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
</div>
</div>
<!-- Welcome Section -->
<div class="mb-6">
<h2 class="text-2xl font-semibold text-white mb-1">Welcome back!</h2>
<p class="text-rustic-500 text-sm">Here's what's happening with your projects today.</p>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div class="bg-rustic-900 rounded-lg p-4 border border-rustic-700">
<div class="flex items-center justify-between mb-2">
<i class="fas fa-project-diagram text-blue-400"></i>
<span class="text-xs text-green-400">+12%</span>
</div>
<div class="text-xl font-semibold text-white">24</div>
<div class="text-xs text-rustic-500">Active Projects</div>
</div>
<div class="bg-rustic-900 rounded-lg p-4 border border-rustic-700">
<div class="flex items-center justify-between mb-2">
<i class="fas fa-users text-purple-400"></i>
<span class="text-xs text-green-400">+5%</span>
</div>
<div class="text-xl font-semibold text-white">156</div>
<div class="text-xs text-rustic-500">Team Members</div>
</div>
<div class="bg-rustic-900 rounded-lg p-4 border border-rustic-700">
<div class="flex items-center justify-between mb-2">
<i class="fas fa-ticket-alt text-yellow-400"></i>
<span class="text-xs text-red-400">-3%</span>
</div>
<div class="text-xl font-semibold text-white">18</div>
<div class="text-xs text-rustic-500">Open Tickets</div>
</div>
<div class="bg-rustic-900 rounded-lg p-4 border border-rustic-700">
<div class="flex items-center justify-between mb-2">
<i class="fas fa-check-circle text-green-400"></i>
<span class="text-xs text-green-400">+8%</span>
</div>
<div class="text-xl font-semibold text-white">142</div>
<div class="text-xs text-rustic-500">Completed</div>
</div>
</div>
<!-- Recent Activity Feed -->
<div class="bg-rustic-900 rounded-lg p-4 border border-rustic-700">
<h3 class="text-sm font-semibold text-gray-300 mb-4">Recent Activity</h3>
<div class="space-y-3" id="activityFeed">
<div class="activity-item">
<div class="activity-dot bg-blue-400"></div>
<div>
<p class="text-sm text-gray-300">New project <span class="text-white font-medium">"Mobile App v2"</span> created</p>
<p class="text-xs text-rustic-500">2 hours ago</p>
</div>
</div>
<div class="activity-item">
<div class="activity-dot bg-green-400"></div>
<div>
<p class="text-sm text-gray-300"><span class="text-white font-medium">Sarah Chen</span> completed task "API Integration"</p>
<p class="text-xs text-rustic-500">4 hours ago</p>
</div>
</div>
<div class="activity-item">
<div class="activity-dot bg-yellow-400"></div>
<p>
<div class="text-sm text-gray-300">Ticket #2847 assigned to <span class="text-white font-medium">Support Team</span></p>
<p class="text-xs text-rustic-500">6 hours ago</p>
</div>
</div>
<div class="activity-item">
<div class="activity-dot bg-purple-400"></div>
<div>
<p class="text-sm text-gray-300"><span class="text-white font-medium">Weekly Report</span> generated successfully</p>
<p class="text-xs text-rustic-500">Yesterday</p>
</div>
</div>
</div>
</div>
</div>
<!-- Resize Handle -->
<div class="resize-handle" id="resizeHandle"></div>
<!-- Bottom Pane: Quick Shortcuts -->
<div class="bottom-pane bg-rustic-800 rounded-xl border border-rustic-700 p-4 overflow-hidden">
<div class="pane-header">
<span class="pane-title">Quick Shortcuts</span>
<button id="openShortcutsModal" class="text-rust hover:text-rust-light transition text-sm flex items-center space-x-1">
<i class="fas fa-edit"></i>
<span>Customize</span>
</button>
</div>
<!-- Shortcuts Grid -->
<div class="shortcuts-grid flex-1 overflow-y-auto" id="shortcutsGrid">
<!-- Shortcuts will be injected here -->
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Chatbot Popup -->
<div class="fixed bottom-6 right-6 z-50 flex flex-col items-end">
<div id="chatbot-panel" class="chatbot-panel closed bg-rustic-800 rounded-xl shadow-2xl border border-rustic-700 w-[380px] h-[500px] mb-4 flex flex-col overflow-hidden">
<div class="bg-rust p-3 flex justify-between items-center shrink-0">
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-white rounded-full animate-pulse"></div>
<span class="font-medium text-white text-sm">Assistant</span>
</div>
<button id="closeChatBtn" class="text-white hover:text-gray-200">
<i class="fas fa-times"></i>
</button>
</div>
<div class="flex-1 bg-black relative">
<iframe src="http://ninjabot/chatbot/YuXRYou2mbnDqCXc" class="w-full h-full" frameborder="0" allow="microphone"></iframe>
</div>
</div>
<button id="chatbot-toggle-btn" class="chatbot-toggle w-14 h-14 bg-rust hover:bg-rust-light rounded-full shadow-lg shadow-rust/40 flex items-center justify-center text-white transform hover:scale-110">
<i class="fas fa-comment-dots text-xl"></i>
</button>
</div>
<!-- Link Settings Modal -->
<div id="linkSettingsModal" class="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center hidden z-[60]">
<div class="bg-rustic-800 border border-rustic-700 rounded-lg p-6 w-full max-w-md shadow-2xl">
<div class="flex justify-between items-center mb-6">
<h3 class="text-xl font-semibold text-white">Edit Launcher</h3>
<button id="modalCloseBtn" class="text-gray-400 hover:text-white transition">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-400 mb-1">Category Title</label>
<input type="text" id="categoryName" class="w-full bg-rustic-900 border border-rustic-700 rounded px-3 py-2 text-white" />
</div>
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Links</label>
<div id="linkItems" class="space-y-3 max-h-64 overflow-y-auto pr-2"></div>
<button id="addNewLinkBtn" class="mt-3 text-rust text-sm flex items-center space-x-1 hover:text-rust-light transition">
<i class="fas fa-plus"></i>
<span>Add New Link</span>
</button>
</div>
</div>
<div class="flex justify-end space-x-3 mt-8">
<button id="modalCancelBtn" class="px-4 py-2 rounded-md bg-rustic-900 hover:bg-rustic-700 text-gray-300 transition border border-rustic-700">Cancel</button>
<button id="saveChangesBtn" class="px-4 py-2 rounded-md bg-rust hover:bg-rust-light text-white transition shadow-lg shadow-rust/20">Save Changes</button>
</div>
</div>
</div>
<!-- Shortcuts Modal -->
<div id="shortcutsModal" class="fixed inset-0 modal-backdrop flex items-center justify-center hidden z-[70]">
<div class="bg-rustic-800 border border-rustic-700 rounded-xl p-6 w-full max-w-lg shadow-2xl max-h-[80vh] flex flex-col">
<div class="flex justify-between items-center mb-6">
<h3 class="text-xl font-semibold text-white">Customize Shortcuts</h3>
<button id="shortcutsModalClose" class="text-gray-400 hover:text-white transition">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4 flex-1 overflow-y-auto pr-2">
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Add New Shortcut</label>
<div class="flex space-x-2 mb-4">
<input type="text" id="shortcutName" placeholder="Name" class="flex-1 bg-rustic-900 border border-rustic-700 rounded px-3 py-2 text-white text-sm" />
<input type="text" id="shortcutUrl" placeholder="URL" class="flex-1 bg-rustic-900 border border-rustic-700 rounded px-3 py-2 text-white text-sm" />
<select id="shortcutIcon" class="bg-rustic-900 border border-rustic-700 rounded px-2 py-2 text-white text-sm">
<option value="fa-link">🔗</option>
<option value="fa-globe">🌐</option>
<option value="fa-tasks"></option>
<option value="fa-chart-line">📈</option>
<option value="fa-cog">⚙️</option>
<option value="fa-envelope">✉️</option>
<option value="fa-calendar">📅</option>
<option value="fa-bell">🔔</option>
<option value="fa-star"></option>
<option value="fa-heart">❤️</option>
<option value="fa-user">👤</option>
<option value="fa-users">👥</option>
<option value="fa-folder">📁</option>
<option value="fa-file">📄</option>
<option value="fa-image">🖼️</option>
<option value="fa-play">▶️</option>
</select>
<select id="shortcutColor" class="bg-rustic-900 border border-rustic-700 rounded px-2 py-2 text-white text-sm">
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="purple">Purple</option>
<option value="yellow">Yellow</option>
<option value="red">Red</option>
<option value="teal">Teal</option>
<option value="rust">Rust</option>
<option value="gray">Gray</option>
</select>
</div>
<button id="addShortcutBtn" class="w-full bg-rustic-700 hover:bg-rustic-600 text-white py-2 rounded-lg transition flex items-center justify-center space-x-2">
<i class="fas fa-plus"></i>
<span>Add Shortcut</span>
</button>
</div>
<div class="border-t border-rustic-700 pt-4">
<label class="block text-sm font-medium text-gray-400 mb-3">Current Shortcuts</label>
<div id="shortcutItems" class="space-y-2 max-h-48 overflow-y-auto"></div>
</div>
</div>
<div class="flex justify-end space-x-3 mt-6 pt-4 border-t border-rustic-700">
<button id="shortcutsModalCancel" class="px-4 py-2 rounded-md bg-rustic-900 hover:bg-rustic-700 text-gray-300 transition border border-rustic-700">Close</button>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// --- State ---
let currentCategory = "";
const storageKey = "dashboardLinks";
const shortcutsStorageKey = "dashboardShortcuts";
const iconClasses = {
blue: "fa-tachometer-alt text-blue-400",
green: "fa-project-diagram text-green-400",
purple: "fa-comments text-purple-400",
yellow: "fa-calendar text-yellow-400",
red: "fa-book text-red-400",
teal: "fa-users text-teal-400",
rust: "fa-link text-rust-light",
gray: "fa-anchor text-gray-400"
};
// Shortcut colors mapping
const shortcutColors = {
blue: "text-blue-400",
green: "text-green-400",
purple: "text-purple-400",
yellow: "text-yellow-400",
red: "text-red-400",
teal: "text-teal-400",
rust: "text-rust-light",
gray: "text-gray-400"
};
const defaultLinks = {
quick: {
title: "Quick Access",
links: [
{ name: "Analytics Dashboard", url: "#", color: "blue" },
{ name: "Project Tracker", url: "#", color: "green" },
{ name: "Team Chat", url: "#", color: "purple" },
],
},
resources: {
title: "Company Hub",
links: [
{ name: "Company Calendar", url: "#", color: "yellow" },
{ name: "Employee Handbook", url: "#", color: "red" },
{ name: "Org Directory", url: "#", color: "teal" },
],
},
support: {
title: "Support Zone",
links: [
{ name: "Submit Ticket", url: "#", color: "rust" },
{ name: "Help Center", url: "#", color: "gray" },
],
}
};
const defaultShortcuts = [
{ name: "Dashboard", url: "#", icon: "fa-tachometer-alt", color: "blue" },
{ name: "Analytics", url: "#", icon: "fa-chart-line", color: "green" },
{ name: "Messages", url: "#", icon: "fa-envelope", color: "purple" },
{ name: "Calendar", url: "#", icon: "fa-calendar", color: "yellow" },
{ name: "Settings", url: "#", icon: "fa-cog", color: "gray" },
{ name: "Support", url: "#", icon: "fa-life-ring", color: "red" },
];
// --- Functions ---
const getStoredLinks = () => {
const links = localStorage.getItem(storageKey);
return links ? JSON.parse(links) : defaultLinks;
};
const saveLinksToStorage = (links) => {
localStorage.setItem(storageKey, JSON.stringify(links));
};
// Shortcuts functions
const getStoredShortcuts = () => {
const shortcuts = localStorage.getItem(shortcutsStorageKey);
return shortcuts ? JSON.parse(shortcuts) : defaultShortcuts;
};
const saveShortcutsToStorage = (shortcuts) => {
localStorage.setItem(shortcutsStorageKey, JSON.stringify(shortcuts));
};
const renderSidebarLinks = () => {
const container = document.getElementById("dashboard-links-view");
const allLinks = getStoredLinks();
container.innerHTML = "";
Object.keys(allLinks).forEach((category) => {
const data = allLinks[category];
const section = document.createElement("div");
section.className = "mb-2";
const header = document.createElement("div");
header.className = "flex justify-between items-center mb-2 px-2";
header.innerHTML = `
<h3 class="font-semibold text-gray-200 text-sm uppercase tracking-wide">${data.title}</h3>
<button class="text-rustic-500 hover:text-rust transition text-xs" data-category="${category}">
<i class="fas fa-cog"></i>
</button>
`;
const list = document.createElement("div");
list.className = "space-y-1";
data.links.forEach(link => {
const icon = iconClasses[link.color] || "fa-link text-gray-400";
const a = document.createElement("a");
a.href = link.url;
a.target = "_blank";
a.className = "link-card flex items-center space-x-3 py-2 px-3 rounded-md text-gray-400 hover:text-white cursor-pointer";
a.innerHTML = `
<i class="fas ${icon} w-4 text-center"></i>
<span class="text-sm truncate">${link.name}</span>
`;
list.appendChild(a);
});
section.appendChild(header);
section.appendChild(list);
container.appendChild(section);
});
document.querySelectorAll("#dashboard-links-view button[data-category]").forEach(btn => {
btn.addEventListener("click", (e) => showLinkSettings(e.currentTarget.dataset.category));
});
};
// Render shortcuts in bottom pane
const renderShortcuts = () => {
const container = document.getElementById("shortcutsGrid");
const shortcuts = getStoredShortcuts();
container.innerHTML = "";
shortcuts.forEach((shortcut, index) => {
const iconColor = shortcutColors[shortcut.color] || "text-gray-400";
const div = document.createElement("div");
div.className = "shortcut-item";
div.innerHTML = `
<i class="fas ${shortcut.icon} ${iconColor}"></i>
<span class="truncate w-full">${shortcut.name}</span>
`;
div.addEventListener("click", () => {
if(shortcut.url && shortcut.url !== "#") {
window.open(shortcut.url, "_blank");
}
});
container.appendChild(div);
});
};
const hideLinkSettings = () => document.getElementById("linkSettingsModal").classList.add("hidden");
const hideShortcutsModal = () => document.getElementById("shortcutsModal").classList.add("hidden");
const showShortcutsModal = () => {
renderShortcutItems();
document.getElementById("shortcutsModal").classList.remove("hidden");
};
const addNewLink = (name = "", url = "", color = "rust") => {
const linkItems = document.getElementById("linkItems");
const newLink = document.createElement("div");
newLink.className = "flex items-center space-x-2 link-item-entry";
let optionsHtml = Object.keys(iconClasses)
.map((key) => `<option value="${key}" ${key === color ? "selected" : ""}>${key.charAt(0).toUpperCase() + key.slice(1)}</option>`)
.join("");
newLink.innerHTML = `
<input type="text" placeholder="Name" class="flex-1 bg-rustic-900 border border-rustic-700 rounded px-2 py-1.5 text-white text-sm" value="${name}">
<input type="text" placeholder="URL" class="flex-1 bg-rustic-900 border border-rustic-700 rounded px-2 py-1.5 text-white text-sm" value="${url}">
<select class="bg-rustic-900 border border-rustic-700 rounded px-1 py-1.5 text-white text-sm">${optionsHtml}</select>
<button class="text-red-500 hover:text-red-400 p-1.5 remove-link-btn"><i class="fas fa-trash"></i></button>
`;
linkItems.appendChild(newLink);
};
const showLinkSettings = (category) => {
currentCategory = category;
const allLinks = getStoredLinks();
const data = allLinks[currentCategory];
document.getElementById("categoryName").value = data.title;
const linkItemsContainer = document.getElementById("linkItems");
linkItemsContainer.innerHTML = "";
if (data.links) {
data.links.forEach(link => addNewLink(link.name, link.url, link.color));
}
document.getElementById("linkSettingsModal").classList.remove("hidden");
};
const saveLinkSettings = () => {
const newTitle = document.getElementById("categoryName").value;
const linkItems = document.querySelectorAll("#linkItems .link-item-entry");
const newLinks = Array.from(linkItems)
.map((item) => ({
name: item.children[0].value,
url: item.children[1].value,
color: item.children[2].value,
}))
.filter((link) => link.name && link.url);
const allLinks = getStoredLinks();
allLinks[currentCategory] = { title: newTitle, links: newLinks };
saveLinksToStorage(allLinks);
renderSidebarLinks();
hideLinkSettings();
};
// Render shortcut items in modal
const renderShortcutItems = () => {
const container = document.getElementById("shortcutItems");
const shortcuts = getStoredShortcuts();
container.innerHTML = "";
shortcuts.forEach((shortcut, index) => {
const iconColor = shortcutColors[shortcut.color] || "text-gray-400";
const div = document.createElement("div");
div.className = "flex items-center justify-between bg-rustic-900 rounded px-3 py-2";
div.innerHTML = `
<div class="flex items-center space-x-3">
<i class="fas ${shortcut.icon} ${iconColor}"></i>
<span class="text-sm text-white">${shortcut.name}</span>
</div>
<button class="text-red-500 hover:text-red-400 remove-shortcut-btn" data-index="${index}">
<i class="fas fa-trash"></i>
</button>
`;
container.appendChild(div);
});
// Attach remove listeners
document.querySelectorAll(".remove-shortcut-btn").forEach(btn => {
btn.addEventListener("click", (e) => {
const idx = parseInt(e.currentTarget.dataset.index);
const shortcuts = getStoredShortcuts();
shortcuts.splice(idx, 1);
saveShortcutsToStorage(shortcuts);
renderShortcutItems();
renderShortcuts();
});
});
};
const addNewShortcut = () => {
const name = document.getElementById("shortcutName").value.trim();
const url = document.getElementById("shortcutUrl").value.trim();
const icon = document.getElementById("shortcutIcon").value;
const color = document.getElementById("shortcutColor").value;
if (!name) {
alert("Please enter a shortcut name");
return;
}
const shortcuts = getStoredShortcuts();
shortcuts.push({ name, url: url || "#", icon, color });
saveShortcutsToStorage(shortcuts);
// Clear inputs
document.getElementById("shortcutName").value = "";
document.getElementById("shortcutUrl").value = "";
renderShortcutItems();
renderShortcuts();
};
// --- Chatbot Logic ---
const chatPanel = document.getElementById('chatbot-panel');
const chatBtn = document.getElementById('chatbot-toggle-btn');
const closeChatBtn = document.getElementById('closeChatBtn');
let isChatOpen = false;
const toggleChat = () => {
isChatOpen = !isChatOpen;
if(isChatOpen) {
chatPanel.classList.remove('closed');
chatBtn.classList.add('hidden');
} else {
chatPanel.classList.add('closed');
setTimeout(() => {
if(!isChatOpen) chatBtn.classList.remove('hidden');
}, 300);
}
};
chatBtn.addEventListener('click', toggleChat);
closeChatBtn.addEventListener('click', toggleChat);
// --- Tab Logic ---
const switchTab = (event) => {
const tabId = event.currentTarget.dataset.tab;
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('block'));
const target = document.getElementById(tabId);
if(target) {
target.classList.remove('hidden');
target.classList.add('block');
}
document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
event.currentTarget.classList.add('active');
};
// --- Search Handlers ---
const handleMetaSearch = (e) => {
e.preventDefault();
const q = document.getElementById('search-input').value;
if(q) window.open(`https://www.google.com/search?q=${encodeURIComponent(q)}`, '_blank');
};
const handleRubyGemsSearch = (e) => {
e.preventDefault();
const q = document.getElementById('rubygems-search-input').value;
if(q) window.open(`https://rubygems.org/search?query=${encodeURIComponent(q)}`, '_blank');
};
// --- Import/Export ---
const handleExport = () => {
const data = localStorage.getItem(storageKey);
if(!data) return;
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([data], {type: 'application/json'}));
a.download = 'links.json';
a.click();
};
const handleImport = (e) => {
const file = e.target.files[0];
if(!file) return;
const r = new FileReader();
r.onload = (ev) => {
try {
const data = JSON.parse(ev.target.result);
localStorage.setItem(storageKey, JSON.stringify(data));
renderSidebarLinks();
alert("Imported successfully");
} catch(err) { alert("Invalid file"); }
};
r.readAsText(file);
e.target.value = '';
};
// --- Pane Resize Logic ---
const setupPaneResize = () => {
const handle = document.getElementById("resizeHandle");
const topPane = document.querySelector(".top-pane");
const bottomPane = document.querySelector(".bottom-pane");
let isResizing = false;
let startY = 0;
let startTopHeight = 0;
handle.addEventListener("mousedown", (e) => {
isResizing = true;
startY = e.clientY;
startTopHeight = topPane.offsetHeight;
document.body.style.cursor = "ns-resize";
document.body.style.userSelect = "none";
});
document.addEventListener("mousemove", (e) => {
if (!isResizing) return;
const deltaY = e.clientY - startY;
const newTopHeight = startTopHeight + deltaY;
const totalHeight = topPane.parentElement.offsetHeight - 16; // Account for gap
if (newTopHeight > 100 && newTopHeight < totalHeight - 150) {
topPane.style.flex = `0 0 ${newTopHeight}px`;
bottomPane.style.flex = `0 0 ${totalHeight - newTopHeight}px`;
}
});
document.addEventListener("mouseup", () => {
if