← back to Web Viewer 3877
public/index.html
543 lines
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Web Viewer - Port 3877</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 1200px;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 24px;
font-weight: 600;
}
.status {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background: #4ade80;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.url-input-container {
padding: 20px 30px;
background: #f8f9fa;
border-bottom: 1px solid #e0e0e0;
}
.url-input-wrapper {
display: flex;
gap: 10px;
}
.url-input {
flex: 1;
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 16px;
transition: all 0.3s;
}
.url-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.load-btn {
padding: 12px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.load-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.load-btn:active {
transform: translateY(0);
}
.content-area {
display: flex;
height: 500px;
}
.web-content {
flex: 1;
padding: 20px 30px;
overflow-y: auto;
background: #fff;
border-right: 1px solid #e0e0e0;
}
.web-content.loading {
display: flex;
justify-content: center;
align-items: center;
color: #999;
}
.web-content h2 {
color: #333;
margin-bottom: 10px;
font-size: 20px;
}
.web-content .meta {
color: #666;
font-size: 14px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #e0e0e0;
}
.web-content .description {
color: #555;
line-height: 1.6;
margin-bottom: 20px;
}
.conversation-area {
flex: 1;
display: flex;
flex-direction: column;
background: #f8f9fa;
}
.messages-container {
flex: 1;
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 15px;
}
.message {
max-width: 80%;
padding: 12px 16px;
border-radius: 15px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message.user {
align-self: flex-end;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.message.bot {
align-self: flex-start;
background: white;
border: 1px solid #e0e0e0;
color: #333;
}
.message-input-container {
padding: 20px;
background: white;
border-top: 1px solid #e0e0e0;
}
.message-input-wrapper {
display: flex;
gap: 10px;
}
.message-input {
flex: 1;
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 25px;
font-size: 15px;
transition: all 0.3s;
}
.message-input:focus {
outline: none;
border-color: #667eea;
}
.send-btn {
padding: 12px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 50%;
width: 45px;
height: 45px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.2s;
}
.send-btn:hover {
transform: scale(1.1);
}
.clear-btn {
padding: 8px 16px;
background: #ef4444;
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
transition: background 0.3s;
}
.clear-btn:hover {
background: #dc2626;
}
.toolbar {
padding: 10px 20px;
background: white;
border-top: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.typing-indicator {
display: none;
padding: 10px 15px;
background: white;
border: 1px solid #e0e0e0;
border-radius: 15px;
align-self: flex-start;
margin: 0 20px;
}
.typing-indicator.show {
display: inline-block;
}
.typing-indicator span {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #999;
margin: 0 2px;
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
}
@media (max-width: 768px) {
.content-area {
flex-direction: column;
height: auto;
}
.web-content {
border-right: none;
border-bottom: 1px solid #e0e0e0;
min-height: 200px;
}
.message {
max-width: 90%;
}
}
</style>
<!-- Meta Pixel — INERT until set-fb-pixel.sh is run (does NOT load fbevents.js or hit Meta until flipped) -->
<script>
(function(){
var PIXEL_ID = '{{FACEBOOK_PIXEL_ID}}';
// Inert until flipped — the brace check prevents BOTH the loader IIFE
// AND the init call from running, so fbevents.js is never fetched.
if (PIXEL_ID.indexOf('{{') !== -1) return;
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init', PIXEL_ID);
fbq('track', 'PageView');
})();
</script>
<!-- noscript fallback is added by set-fb-pixel.sh on flip (omitted here to avoid unflipped img phone-home) -->
<!-- End Meta Pixel -->
</head>
<body>
<div class="container">
<div class="header">
<h1>🌐 Interactive Web Viewer</h1>
<div class="status">
<div class="status-indicator"></div>
<span>Connected</span>
</div>
</div>
<div class="url-input-container">
<div class="url-input-wrapper">
<input
type="url"
class="url-input"
id="urlInput"
placeholder="Enter a URL to load and explore..."
value="https://example.com"
>
<button class="load-btn" id="loadBtn">Load Page</button>
</div>
</div>
<div class="content-area">
<div class="web-content loading" id="webContent">
<div>Enter a URL above to load web content</div>
</div>
<div class="conversation-area">
<div class="messages-container" id="messagesContainer"></div>
<div class="typing-indicator" id="typingIndicator">
<span></span>
<span></span>
<span></span>
</div>
<div class="message-input-container">
<div class="message-input-wrapper">
<input
type="text"
class="message-input"
id="messageInput"
placeholder="Ask about the loaded content..."
disabled
>
<button class="send-btn" id="sendBtn" disabled>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
</svg>
</button>
</div>
</div>
<div class="toolbar">
<div class="info">
<span id="messageCount">0 messages</span>
</div>
<button class="clear-btn" id="clearBtn">Clear Chat</button>
</div>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const urlInput = document.getElementById('urlInput');
const loadBtn = document.getElementById('loadBtn');
const webContent = document.getElementById('webContent');
const messagesContainer = document.getElementById('messagesContainer');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const clearBtn = document.getElementById('clearBtn');
const messageCount = document.getElementById('messageCount');
const typingIndicator = document.getElementById('typingIndicator');
let messageCounter = 0;
let currentContent = null;
// Load URL
loadBtn.addEventListener('click', loadUrl);
urlInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') loadUrl();
});
function loadUrl() {
const url = urlInput.value.trim();
if (!url) {
alert('Please enter a valid URL');
return;
}
webContent.innerHTML = '<div>Loading content...</div>';
webContent.classList.add('loading');
socket.emit('load-url', url);
}
// Handle content loaded
socket.on('content-loaded', (content) => {
currentContent = content;
webContent.classList.remove('loading');
webContent.innerHTML = `
<h2>${content.title || 'Untitled Page'}</h2>
<div class="meta">
<div>URL: ${content.url}</div>
<div>Loaded: ${new Date(content.timestamp).toLocaleString()}</div>
</div>
${content.description ? `<div class="description">${content.description}</div>` : ''}
<div class="content-preview">
${content.content.substring(0, 1000)}${content.content.length > 1000 ? '...' : ''}
</div>
`;
// Enable chat
messageInput.disabled = false;
sendBtn.disabled = false;
messageInput.focus();
});
// Handle bot messages
socket.on('bot-message', (data) => {
typingIndicator.classList.remove('show');
addMessage(data.message, 'bot');
});
// Send message
function sendMessage() {
const message = messageInput.value.trim();
if (!message) return;
addMessage(message, 'user');
socket.emit('user-message', message);
messageInput.value = '';
// Show typing indicator
setTimeout(() => {
typingIndicator.classList.add('show');
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}, 200);
}
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Add message to chat
function addMessage(text, sender) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}`;
messageDiv.textContent = text;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
messageCounter++;
messageCount.textContent = `${messageCounter} message${messageCounter !== 1 ? 's' : ''}`;
}
// Clear conversation
clearBtn.addEventListener('click', () => {
if (confirm('Clear all messages?')) {
messagesContainer.innerHTML = '';
messageCounter = 0;
messageCount.textContent = '0 messages';
socket.emit('clear-conversation');
}
});
// Handle errors
socket.on('error', (error) => {
webContent.classList.remove('loading');
webContent.innerHTML = `<div style="color: #ef4444;">Error: ${error.message}</div>`;
});
socket.on('conversation-cleared', () => {
console.log('Conversation cleared on server');
});
// Auto-load example on page load
window.addEventListener('load', () => {
setTimeout(() => {
loadBtn.click();
}, 500);
});
</script>
</body>
</html>