21 / public /index.html
senku21230's picture
Create public/index.html
e256ede verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>God-Mode Browser Dashboard</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Base styles and scrollbar hiding */
body {
-webkit-tap-highlight-color: transparent;
background-color: #09090b; /* Very dark background */
font-family: 'Sora', sans-serif;
}
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* Smooth Animations */
.scale-up {
animation: scaleUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.fade-in {
animation: fadeIn 0.3s ease-out forwards;
}
@keyframes scaleUp {
from { transform: scale(0.9) translateY(15px); opacity: 0; }
to { transform: scale(1) translateY(0); opacity: 1; }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Ambient glow for the active tabs */
.tab-glow {
position: absolute;
width: 100px;
height: 100px;
background: radial-gradient(circle, rgba(255,111,76,0.15) 0%, rgba(0,0,0,0) 70%);
top: -30px;
right: -30px;
pointer-events: none;
}
/* Zero-Latency Video Streaming Container */
#stream-container {
width: 100%;
height: calc(100vh - 5rem);
position: absolute;
top: 5rem;
left: 0;
z-index: 10;
display: none;
background-color: #000;
}
#stream-image {
width: 100%;
height: 100%;
object-fit: contain;
pointer-events: auto;
}
</style>
</head>
<body class="text-white h-screen w-screen overflow-hidden flex flex-col fade-in">
<div class="h-20 flex items-center justify-between px-5 z-20 shrink-0 bg-[#09090b]/90 backdrop-blur-md border-b border-gray-800/50">
<button id="toggle-tabs-btn" class="px-4 py-2 bg-[#18181b] border border-gray-800 rounded-lg text-sm font-semibold flex items-center gap-2 hover:bg-[#27272a] transition-all shadow-lg active:scale-95">
<span id="tab-count-badge" class="w-5 h-5 bg-[#ff6f4c] text-white rounded-full flex items-center justify-center text-[10px] shadow-sm">1</span>
<span id="tab-toggle-text">Active Tabs</span>
</button>
<div id="connection-status" class="flex items-center gap-2 text-xs font-medium text-gray-400">
<div id="status-dot" class="w-2 h-2 rounded-full bg-red-500 animate-pulse"></div>
<span id="status-text">Connecting...</span>
</div>
<button id="add-tab-btn" class="p-2 flex items-center justify-center text-gray-400 hover:text-white transition-transform active:scale-90">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
</div>
<div id="stream-container" class="fade-in">
<img id="stream-image" src="" alt="Awaiting Stream..."/>
</div>
<div id="tab-scroll-area" class="flex-1 overflow-y-auto hide-scrollbar p-6 flex flex-col items-center justify-start pt-2 pb-24 cursor-default z-30 bg-[#09090b]">
<div id="tabs-grid" class="w-full">
</div>
</div>
<script>
// --- State Management ---
let tabs = [{ id: Date.now(), title: "Initial Tab" }];
let activeTabId = tabs[0].id;
let showingTabs = true;
// --- DOM Elements ---
const tabScrollArea = document.getElementById('tab-scroll-area');
const tabsGrid = document.getElementById('tabs-grid');
const addTabBtn = document.getElementById('add-tab-btn');
const toggleTabsBtn = document.getElementById('toggle-tabs-btn');
const streamContainer = document.getElementById('stream-container');
const streamImage = document.getElementById('stream-image');
const tabCountBadge = document.getElementById('tab-count-badge');
const statusDot = document.getElementById('status-dot');
const statusText = document.getElementById('status-text');
// --- WebSocket Connection Engine ---
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${protocol}//${window.location.host}`);
ws.onopen = () => {
statusDot.classList.replace('bg-red-500', 'bg-green-500');
statusDot.classList.remove('animate-pulse');
statusText.innerText = "Engine Online";
statusText.classList.replace('text-gray-400', 'text-green-400');
};
ws.onclose = () => {
statusDot.classList.replace('bg-green-500', 'bg-red-500');
statusText.innerText = "Disconnected";
statusText.classList.replace('text-green-400', 'text-red-400');
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
// Ultra-fast CDP Frame Rendering
if (message.type === 'stream_frame') {
streamImage.src = 'data:image/jpeg;base64,' + message.data;
}
};
// --- View Controller (Tabs vs Stream) ---
function switchView(toTabs) {
showingTabs = toTabs;
if (toTabs) {
tabScrollArea.style.display = 'flex';
streamContainer.style.display = 'none';
} else {
tabScrollArea.style.display = 'none';
streamContainer.style.display = 'block';
}
tabCountBadge.innerText = tabs.length;
}
toggleTabsBtn.addEventListener('click', () => switchView(!showingTabs));
// --- UI Rendering Logic ---
const getTabInnerHtml = (tab) => `
<div class="w-full h-full bg-[#18181b] rounded-2xl border ${tab.id === activeTabId ? 'border-[#ff6f4c]' : 'border-gray-800/80'} hover:border-gray-600 transition-colors overflow-hidden relative flex flex-col shadow-xl pointer-events-auto">
${tab.id === activeTabId ? '<div class="tab-glow"></div>' : ''}
<div class="flex items-center justify-between px-3 py-2.5 bg-[#121214] border-b border-gray-800/50">
<div class="flex items-center gap-2.5">
<div class="w-5 h-5 bg-[#ff6f4c] rounded-md flex items-center justify-center shadow-sm">
<span class="text-[10px] font-bold text-white">${tab.title.charAt(0)}</span>
</div>
<span class="text-xs font-semibold text-gray-200 tracking-wide">${tab.title}</span>
</div>
<button class="close-tab-btn w-6 h-6 flex items-center justify-center text-gray-500 hover:text-red-400 hover:bg-red-400/10 rounded-full transition-colors" data-id="${tab.id}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<div class="flex-1 p-4 flex flex-col items-center justify-center bg-[#18181b] select-tab-zone cursor-pointer">
<div class="w-14 h-14 rounded-[1rem] bg-[#1f1f23] flex items-center justify-center mb-5 border border-gray-800/60 shadow-inner">
<svg class="w-6 h-6 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</div>
<span class="text-[10px] text-gray-500 font-bold tracking-widest uppercase">Tap to View</span>
</div>
</div>
`;
function renderTabs() {
tabsGrid.innerHTML = '';
tabCountBadge.innerText = tabs.length;
if(tabs.length === 0) {
tabsGrid.innerHTML = `<div class="text-center text-gray-500 mt-20 pointer-events-auto"><p class="text-lg font-medium">System Idle. No Tabs Open.</p></div>`;
tabsGrid.className = "w-full max-w-[340px] mx-auto flex flex-col items-center justify-center h-full";
return;
}
// Grid Layout Adaptation
tabsGrid.className = tabs.length === 1 ? "w-full max-w-[240px] mx-auto flex flex-col gap-6" : "w-full max-w-[340px] mx-auto grid grid-cols-2 gap-4";
tabs.forEach((tab, index) => {
const tabEl = document.createElement('div');
let classes = "relative rounded-2xl p-[1px] transform transition-all duration-300 hover:scale-[1.02] active:scale-95 scale-up ";
classes += "bg-gradient-to-br from-gray-700/50 to-gray-900 hover:from-[#ff6f4c] hover:to-[#ff6f4c]/50 ";
classes += tabs.length === 1 ? "w-full aspect-[4/5]" : "w-full aspect-[3/4]";
tabEl.className = classes;
tabEl.style.animationDelay = `${index * 0.05}s`;
tabEl.innerHTML = getTabInnerHtml(tab);
// Enter Stream Mode Action
tabEl.querySelector('.select-tab-zone').addEventListener('click', () => {
activeTabId = tab.id;
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'switch_tab', tabId: activeTabId }));
}
switchView(false); // Switch to video stream
renderTabs(); // Update active border color
});
// Close Tab Action
tabEl.querySelector('.close-tab-btn').addEventListener('click', (e) => {
e.stopPropagation();
tabs = tabs.filter(t => t.id !== tab.id);
renderTabs();
});
tabsGrid.appendChild(tabEl);
});
}
// Add New Tab Action
addTabBtn.addEventListener('click', () => {
const newTabId = Date.now();
const newTab = { id: newTabId, title: `Target Payload` };
tabs.push(newTab);
activeTabId = newTabId;
// Dispatch command to Node.js backend to spawn puppeteer page
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'new_tab', tabId: newTabId }));
}
renderTabs();
// Auto scroll to bottom
setTimeout(() => {
tabScrollArea.scrollTo({ top: tabScrollArea.scrollHeight, behavior: 'smooth' });
}, 50);
switchView(false); // Automatically drop into the newly spawned tab stream
});
// Initialize First State
renderTabs();
switchView(false); // Automatically show the stream of the first tab on load
</script>
</body>
</html>