← 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>