| <!DOCTYPE html> |
| <html lang="en" class="scroll-smooth"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Phantom Digital - AI Workspace</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://unpkg.com/lucide@latest"></script> |
| <script> |
| tailwind.config = { |
| darkMode: 'class', |
| theme: { |
| extend: { |
| colors: { |
| primary: { |
| 50: '#eff6ff', |
| 100: '#dbeafe', |
| 500: '#3b82f6', |
| 600: '#2563eb', |
| 700: '#1d4ed8', |
| 900: '#1e3a8a', |
| } |
| } |
| } |
| } |
| } |
| </script> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); |
| body { font-family: 'Inter', sans-serif; } |
| .glass-effect { |
| background: rgba(255, 255, 255, 0.05); |
| backdrop-filter: blur(10px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| .dark .glass-effect { |
| background: rgba(0, 0, 0, 0.2); |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| } |
| .model-card:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); |
| } |
| .chat-bubble { |
| max-width: 80%; |
| word-wrap: break-word; |
| } |
| .chat-bubble-user { |
| background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); |
| color: white; |
| border-radius: 18px 18px 4px 18px; |
| } |
| .chat-bubble-ai { |
| background: rgba(255, 255, 255, 0.1); |
| border: 1px solid rgba(255, 255, 255, 0.2); |
| border-radius: 18px 18px 18px 4px; |
| } |
| .canvas-grid { |
| background-image: |
| linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px), |
| linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px); |
| background-size: 20px 20px; |
| } |
| .typing-indicator span { |
| animation: typing 1.4s infinite; |
| display: inline-block; |
| width: 6px; |
| height: 6px; |
| background: currentColor; |
| border-radius: 50%; |
| margin: 0 2px; |
| } |
| .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(-6px); } |
| } |
| </style> |
| </head> |
| <body class="bg-slate-900 text-white h-screen overflow-hidden"> |
|
|
| |
| <header class="h-14 border-b border-slate-700 bg-slate-900/95 backdrop-blur-md flex items-center justify-between px-4 z-50"> |
| <div class="flex items-center gap-3"> |
| <div class="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center"> |
| <i data-lucide="zap" class="text-white w-5 h-5"></i> |
| </div> |
| <span class="font-bold text-lg">Phantom Digital</span> |
| <span class="text-xs bg-blue-600/20 text-blue-400 px-2 py-0.5 rounded-full border border-blue-600/30">Beta</span> |
| </div> |
| |
| <div class="flex items-center gap-4"> |
| <button onclick="toggleDarkMode()" class="p-2 rounded-lg hover:bg-slate-800 transition-colors"> |
| <i data-lucide="sun" class="hidden dark:block w-5 h-5 text-yellow-400"></i> |
| <i data-lucide="moon" class="block dark:hidden w-5 h-5 text-slate-400"></i> |
| </button> |
| <a href="index.html" class="flex items-center gap-2 text-sm text-slate-400 hover:text-white transition-colors"> |
| <i data-lucide="home" class="w-4 h-4"></i> |
| Exit |
| </a> |
| <div class="w-8 h-8 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-xs font-bold"> |
| JD |
| </div> |
| </div> |
| </header> |
|
|
| |
| <div class="flex h-[calc(100vh-56px)]"> |
| |
| |
| <aside class="w-1/3 min-w-[350px] max-w-[450px] border-r border-slate-700 flex flex-col bg-slate-900"> |
| |
| |
| <div class="p-4 border-b border-slate-700"> |
| <label class="text-xs text-slate-400 uppercase tracking-wider font-semibold mb-2 block">Select AI Model</label> |
| <div class="relative"> |
| <button onclick="toggleModelMenu()" class="w-full flex items-center justify-between p-3 rounded-xl bg-slate-800 border border-slate-700 hover:border-blue-500 transition-colors text-left"> |
| <div class="flex items-center gap-3"> |
| <div class="w-8 h-8 rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 flex items-center justify-center" id="currentModelIcon"> |
| <i data-lucide="cpu" class="w-4 h-4 text-white"></i> |
| </div> |
| <div> |
| <div class="font-semibold text-sm" id="currentModelName">Groq Llama 3.3 70B</div> |
| <div class="text-xs text-slate-400">Fastest inference</div> |
| </div> |
| </div> |
| <i data-lucide="chevron-down" class="w-5 h-5 text-slate-400"></i> |
| </button> |
| |
| |
| <div id="modelMenu" class="hidden absolute top-full left-0 right-0 mt-2 bg-slate-800 border border-slate-700 rounded-xl shadow-2xl z-50 max-h-96 overflow-y-auto"> |
| <div class="p-2"> |
| <div class="text-xs text-slate-500 uppercase tracking-wider font-semibold px-3 py-2">Free Tier Models</div> |
| |
| |
| <button onclick="selectModel('groq', 'Groq Llama 3.3 70B', 'Fastest inference', 'cpu', 'from-green-500 to-emerald-600')" class="w-full p-3 rounded-lg hover:bg-slate-700 transition-colors text-left group"> |
| <div class="flex items-center justify-between mb-1"> |
| <span class="font-medium text-sm">Groq Llama 3.3 70B</span> |
| <span class="text-xs bg-green-500/20 text-green-400 px-2 py-0.5 rounded">Free</span> |
| </div> |
| <div class="text-xs text-slate-400">Lightning fast • 800+ tokens/sec</div> |
| </button> |
| |
| <button onclick="selectModel('groq-mixtral', 'Groq Mixtral 8x7B', 'Multilingual expert', 'globe', 'from-green-500 to-emerald-600')" class="w-full p-3 rounded-lg hover:bg-slate-700 transition-colors text-left group"> |
| <div class="flex items-center justify-between mb-1"> |
| <span class="font-medium text-sm">Groq Mixtral 8x7B</span> |
| <span class="text-xs bg-green-500/20 text-green-400 px-2 py-0.5 rounded">Free</span> |
| </div> |
| <div class="text-xs text-slate-400">MOE architecture • 32k context</div> |
| </button> |
| |
| |
| <button onclick="selectModel('cerebras', 'Cerebras Llama 3.1 70B', 'Wafer-scale compute', 'chip', 'from-orange-500 to-red-600')" class="w-full p-3 rounded-lg hover:bg-slate-700 transition-colors text-left group"> |
| <div class="flex items-center justify-between mb-1"> |
| <span class="font-medium text-sm">Cerebras Llama 3.1 70B</span> |
| <span class="text-xs bg-orange-500/20 text-orange-400 px-2 py-0.5 rounded">Free</span> |
| </div> |
| <div class="text-xs text-slate-400">Wafer-scale engine • 16K batch</div> |
| </button> |
| |
| |
| <button onclick="selectModel('sambanova', 'SambaNova Llama 3.1 405B', 'Enterprise grade', 'server', 'from-purple-500 to-indigo-600')" class="w-full p-3 rounded-lg hover:bg-slate-700 transition-colors text-left group"> |
| <div class="flex items-center justify-between mb-1"> |
| <span class="font-medium text-sm">SambaNova Llama 3.1 405B</span> |
| <span class="text-xs bg-purple-500/20 text-purple-400 px-2 py-0.5 rounded">Free</span> |
| </div> |
| <div class="text-xs text-slate-400">DataScale SN40L • RDU architecture</div> |
| </button> |
| |
| |
| <button onclick="selectModel('deepseek', 'DeepSeek V3', 'Reasoning champion', 'brain', 'from-blue-500 to-cyan-600')" class="w-full p-3 rounded-lg hover:bg-slate-700 transition-colors text-left group"> |
| <div class="flex items-center justify-between mb-1"> |
| <span class="font-medium text-sm">DeepSeek V3</span> |
| <span class="text-xs bg-blue-500/20 text-blue-400 px-2 py-0.5 rounded">Free</span> |
| </div> |
| <div class="text-xs text-slate-400">671B parameters • MOE</div> |
| </button> |
| |
| <button onclick="selectModel('deepseek-chat', 'DeepSeek Chat V2.5', 'Chinese & English', 'message-square', 'from-blue-500 to-cyan-600')" class="w-full p-3 rounded-lg hover:bg-slate-700 transition-colors text-left group"> |
| <div class="flex items-center justify-between mb-1"> |
| <span class="font-medium text-sm">DeepSeek Chat V2.5</span> |
| <span class="text-xs bg-blue-500/20 text-blue-400 px-2 py-0.5 rounded">Free</span> |
| </div> |
| <div class="text-xs text-slate-400">Bilingual expert • 128K context</div> |
| </button> |
| |
| |
| <button onclick="selectModel('google', 'Gemini 2.0 Flash', 'Multimodal wonder', 'sparkles', 'from-yellow-500 to-orange-600')" class="w-full p-3 rounded-lg hover:bg-slate-700 transition-colors text-left group"> |
| <div class="flex items-center justify-between mb-1"> |
| <span class="font-medium text-sm">Gemini 2.0 Flash</span> |
| <span class="text-xs bg-yellow-500/20 text-yellow-400 px-2 py-0.5 rounded">Free</span> |
| </div> |
| <div class="text-xs text-slate-400">Native multimodal • 1M context</div> |
| </button> |
| |
| <button onclick="selectModel('google-pro', 'Gemini 1.5 Pro', 'Advanced reasoning', 'star', 'from-yellow-500 to-orange-600')" class="w-full p-3 rounded-lg hover:bg-slate-700 transition-colors text-left group"> |
| <div class="flex items-center justify-between mb-1"> |
| <span class="font-medium text-sm">Gemini 1.5 Pro</span> |
| <span class="text-xs bg-yellow-500/20 text-yellow-400 px-2 py-0.5 rounded">Free</span> |
| </div> |
| <div class="text-xs text-slate-400">Complex tasks • 2M context</div> |
| </button> |
| |
| |
| <button onclick="selectModel('openrouter', 'OpenRouter Auto', 'Best model routing', 'route', 'from-pink-500 to-rose-600')" class="w-full p-3 rounded-lg hover:bg-slate-700 transition-colors text-left group"> |
| <div class="flex items-center justify-between mb-1"> |
| <span class="font-medium text-sm">OpenRouter Auto</span> |
| <span class="text-xs bg-pink-500/20 text-pink-400 px-2 py-0.5 rounded">Free</span> |
| </div> |
| <div class="text-xs text-slate-400">Intelligent routing • Multiple providers</div> |
| </button> |
| |
| |
| <button onclick="selectModel('mistral', 'Mistral Large 2', 'European excellence', 'mountain', 'from-teal-500 to-cyan-600')" class="w-full p-3 rounded-lg hover:bg-slate-700 transition-colors text-left group"> |
| <div class="flex items-center justify-between mb-1"> |
| <span class="font-medium text-sm">Mistral Large 2</span> |
| <span class="text-xs bg-teal-500/20 text-teal-400 px-2 py-0.5 rounded">Free</span> |
| </div> |
| <div class="text-xs text-slate-400">123B parameters • Code expert</div> |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="chatContainer" class="flex-1 overflow-y-auto p-4 space-y-4"> |
| |
| <div class="flex gap-3"> |
| <div class="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex-shrink-0 flex items-center justify-center"> |
| <i data-lucide="bot" class="w-4 h-4 text-white"></i> |
| </div> |
| <div class="chat-bubble chat-bubble-ai p-4 text-sm leading-relaxed"> |
| <p class="mb-2">👋 Welcome to <strong>Phantom Digital</strong>!</p> |
| <p class="mb-2">I'm your AI assistant powered by the world's best free-tier models. Select your preferred model from the dropdown above and start creating.</p> |
| <p>I can help you with:</p> |
| <ul class="list-disc list-inside mt-1 text-slate-300 space-y-0.5"> |
| <li>Code generation & debugging</li> |
| <li>Creative writing & content</li> |
| <li>Data analysis & visualization</li> |
| <li>Image & video concepts</li> |
| <li>3D modeling guidance</li> |
| </ul> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="typingIndicator" class="hidden px-4 py-2"> |
| <div class="flex gap-3"> |
| <div class="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex-shrink-0 flex items-center justify-center"> |
| <i data-lucide="bot" class="w-4 h-4 text-white"></i> |
| </div> |
| <div class="chat-bubble chat-bubble-ai p-3 flex items-center gap-1"> |
| <div class="typing-indicator text-slate-400"> |
| <span></span> |
| <span></span> |
| <span></span> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="p-4 border-t border-slate-700 bg-slate-900"> |
| <div class="relative"> |
| <textarea |
| id="messageInput" |
| rows="1" |
| placeholder="Ask anything... (Shift+Enter for new line)" |
| class="w-full bg-slate-800 border border-slate-700 rounded-xl px-4 py-3 pr-12 text-sm resize-none focus:outline-none focus:border-blue-500 transition-colors max-h-32" |
| onkeydown="handleKeyDown(event)" |
| oninput="autoResize(this)" |
| ></textarea> |
| <button |
| onclick="sendMessage()" |
| class="absolute right-2 bottom-2 p-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors text-white" |
| id="sendButton" |
| > |
| <i data-lucide="send" class="w-4 h-4"></i> |
| </button> |
| </div> |
| <div class="flex items-center justify-between mt-2 text-xs text-slate-500"> |
| <span id="modelStatus">Groq • 70B • Free tier</span> |
| <span>Enter to send</span> |
| </div> |
| </div> |
| </aside> |
|
|
| |
| <main class="flex-1 relative bg-slate-950 canvas-grid overflow-hidden"> |
| |
| <div class="absolute top-4 left-4 right-4 flex items-center justify-between z-10"> |
| <div class="flex items-center gap-2 glass-effect rounded-lg p-1.5"> |
| <button onclick="setTool('select')" class="p-2 rounded-md hover:bg-white/10 transition-colors text-blue-400" title="Select" id="tool-select"> |
| <i data-lucide="mouse-pointer-2" class="w-4 h-4"></i> |
| </button> |
| <button onclick="setTool('pen')" class="p-2 rounded-md hover:bg-white/10 transition-colors text-slate-400" title="Pen" id="tool-pen"> |
| <i data-lucide="pen-tool" class="w-4 h-4"></i> |
| </button> |
| <button onclick="setTool('rectangle')" class="p-2 rounded-md hover:bg-white/10 transition-colors text-slate-400" title="Rectangle" id="tool-rectangle"> |
| <i data-lucide="square" class="w-4 h-4"></i> |
| </button> |
| <button onclick="setTool('circle')" class="p-2 rounded-md hover:bg-white/10 transition-colors text-slate-400" title="Circle" id="tool-circle"> |
| <i data-lucide="circle" class="w-4 h-4"></i> |
| </button> |
| <button onclick="setTool('text')" class="p-2 rounded-md hover:bg-white/10 transition-colors text-slate-400" title="Text" id="tool-text"> |
| <i data-lucide="type" class="w-4 h-4"></i> |
| </button> |
| <div class="w-px h-6 bg-white/20 mx-1"></div> |
| <button onclick="clearCanvas()" class="p-2 rounded-md hover:bg-red-500/20 transition-colors text-red-400" title="Clear"> |
| <i data-lucide="trash-2" class="w-4 h-4"></i> |
| </button> |
| <button onclick="downloadCanvas()" class="p-2 rounded-md hover:bg-white/10 transition-colors text-slate-400" title="Download"> |
| <i data-lucide="download" class="w-4 h-4"></i> |
| </button> |
| </div> |
|
|
| <div class="glass-effect rounded-lg px-4 py-2 text-xs font-medium"> |
| <span id="canvasSize">1200 x 800 px</span> |
| </div> |
| </div> |
|
|
| |
| <div id="canvasContainer" class="absolute inset-0 flex items-center justify-center p-8"> |
| <canvas id="mainCanvas" class="bg-white dark:bg-slate-900 rounded-lg shadow-2xl cursor-crosshair" width="1200" height="800"></canvas> |
| </div> |
|
|
| |
| <div class="absolute bottom-4 right-4 glass-effect rounded-lg p-1.5 flex items-center gap-1"> |
| <button onclick="zoomCanvas(-0.1)" class="p-2 rounded-md hover:bg-white/10 transition-colors"> |
| <i data-lucide="minus" class="w-4 h-4"></i> |
| </button> |
| <span id="zoomLevel" class="text-xs font-medium w-12 text-center">100%</span> |
| <button onclick="zoomCanvas(0.1)" class="p-2 rounded-md hover:bg-white/10 transition-colors"> |
| <i data-lucide="plus" class="w-4 h-4"></i> |
| </button> |
| </div> |
|
|
| |
| <div class="absolute bottom-4 left-4 glass-effect rounded-lg p-3 max-w-xs text-xs text-slate-400"> |
| <p class="font-medium text-white mb-1">Canvas Tips:</p> |
| <ul class="space-y-1 list-disc list-inside"> |
| <li>Use tools to draw shapes</li> |
| <li>AI can generate code from your drawings</li> |
| <li>Export as PNG or SVG</li> |
| </ul> |
| </div> |
| </main> |
| </div> |
|
|
| <script> |
| |
| lucide.createIcons(); |
| |
| |
| function toggleDarkMode() { |
| const html = document.documentElement; |
| if (html.classList.contains('dark')) { |
| html.classList.remove('dark'); |
| localStorage.setItem('theme', 'light'); |
| } else { |
| html.classList.add('dark'); |
| localStorage.setItem('theme', 'dark'); |
| } |
| } |
| |
| if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) { |
| document.documentElement.classList.add('dark'); |
| } |
| |
| |
| let currentModel = 'groq'; |
| |
| function toggleModelMenu() { |
| const menu = document.getElementById('modelMenu'); |
| menu.classList.toggle('hidden'); |
| } |
| |
| function selectModel(id, name, desc, icon, gradient) { |
| currentModel = id; |
| document.getElementById('currentModelName').textContent = name; |
| document.getElementById('modelStatus').textContent = name.split(' ')[0] + ' • Free tier'; |
| |
| const iconDiv = document.getElementById('currentModelIcon'); |
| iconDiv.className = `w-8 h-8 rounded-lg bg-gradient-to-br ${gradient} flex items-center justify-center`; |
| |
| toggleModelMenu(); |
| |
| |
| addSystemMessage(`Switched to ${name}`); |
| } |
| |
| function addSystemMessage(text) { |
| const container = document.getElementById('chatContainer'); |
| const div = document.createElement('div'); |
| div.className = 'flex justify-center'; |
| div.innerHTML = ` |
| <span class="text-xs text-slate-500 bg-slate-800 px-3 py-1 rounded-full">${text}</span> |
| `; |
| container.appendChild(div); |
| container.scrollTop = container.scrollHeight; |
| } |
| |
| |
| document.addEventListener('click', (e) => { |
| const menu = document.getElementById('modelMenu'); |
| const button = e.target.closest('button'); |
| if (!button || !button.onclick || button.onclick.toString().includes('toggleModelMenu')) { |
| if (!e.target.closest('#modelMenu')) { |
| menu.classList.add('hidden'); |
| } |
| } |
| }); |
| |
| |
| let isTyping = false; |
| |
| function handleKeyDown(e) { |
| if (e.key === 'Enter' && !e.shiftKey) { |
| e.preventDefault(); |
| sendMessage(); |
| } |
| } |
| |
| function autoResize(textarea) { |
| textarea.style.height = 'auto'; |
| textarea.style.height = Math.min(textarea.scrollHeight, 128) + 'px'; |
| } |
| |
| function sendMessage() { |
| const input = document.getElementById('messageInput'); |
| const message = input.value.trim(); |
| |
| if (!message || isTyping) return; |
| |
| |
| addMessage(message, 'user'); |
| input.value = ''; |
| input.style.height = 'auto'; |
| |
| |
| showTyping(); |
| |
| |
| setTimeout(() => { |
| hideTyping(); |
| generateAIResponse(message); |
| }, 1500 + Math.random() * 1500); |
| } |
| |
| function addMessage(text, sender) { |
| const container = document.getElementById('chatContainer'); |
| const div = document.createElement('div'); |
| div.className = 'flex gap-3 ' + (sender === 'user' ? 'flex-row-reverse' : ''); |
| |
| if (sender === 'user') { |
| div.innerHTML = ` |
| <div class="w-8 h-8 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex-shrink-0 flex items-center justify-center text-xs font-bold"> |
| You |
| </div> |
| <div class="chat-bubble chat-bubble-user p-4 text-sm leading-relaxed"> |
| ${escapeHtml(text)} |
| </div> |
| `; |
| } else { |
| div.innerHTML = ` |
| <div class="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex-shrink-0 flex items-center justify-center"> |
| <i data-lucide="bot" class="w-4 h-4 text-white"></i> |
| </div> |
| <div class="chat-bubble chat-bubble-ai p-4 text-sm leading-relaxed text-slate-200"> |
| ${text} |
| </div> |
| `; |
| } |
| |
| container.appendChild(div); |
| container.scrollTop = container.scrollHeight; |
| lucide.createIcons(); |
| } |
| |
| function showTyping() { |
| isTyping = true; |
| document.getElementById('typingIndicator').classList.remove('hidden'); |
| document.getElementById('chatContainer').scrollTop = document.getElementById('chatContainer').scrollHeight; |
| } |
| |
| function hideTyping() { |
| isTyping = false; |
| document.getElementById('typingIndicator').classList.add('hidden'); |
| } |
| |
| function generateAIResponse(userMessage) { |
| const responses = { |
| 'groq': "Using Groq's ultra-fast inference, I can process that instantly. The Llama 3.3 70B model provides excellent reasoning capabilities with blazing speed.", |
| 'cerebras': "Cerebras' wafer-scale compute allows me to handle complex computations efficiently. Their CS-3 system delivers unmatched training and inference performance.", |
| 'sambanova': "SambaNova's DataScale system with RDU architecture is processing your request using the massive 405B parameter model for maximum accuracy.", |
| 'deepseek': "DeepSeek V3's MoE architecture with 671B parameters is analyzing your request with deep reasoning capabilities.", |
| 'google': "Gemini's native multimodal capabilities allow me to understand and process complex requests across text, code, and visual contexts.", |
| 'default': "I've processed your request using " + currentModel + ". The canvas on the right can be used to visualize concepts or sketch ideas. Would you like me to generate code based on a drawing, or help you create something specific?" |
| }; |
| |
| const lowerMsg = userMessage.toLowerCase(); |
| let response = responses['default']; |
| |
| if (lowerMsg.includes('hello') || lowerMsg.includes('hi')) { |
| response = "Hello! I'm ready to help you create. You can draw on the canvas and I'll analyze it, or ask me to generate code, content, or ideas."; |
| } else if (lowerMsg.includes('code') || lowerMsg.includes('program')) { |
| response = "I can generate code in any language. Based on your current project, would you like me to create:\n\n• React/Vue components\n• Python scripts for data analysis\n• CSS animations\n• API endpoints\n• Or something else?\n\nJust describe what you need!"; |
| } else if (lowerMsg.includes('draw') || lowerMsg.includes('canvas')) { |
| response = "The canvas supports drawing shapes, freehand sketching, and text. Use the toolbar at the top to select tools. I can analyze your drawings and convert them to code or provide feedback!"; |
| } else if (responses[currentModel]) { |
| response = responses[currentModel] + " How can I assist with your project today?"; |
| } |
| |
| |
| response = response.replace(/\n/g, '<br>').replace(/•/g, '•'); |
| addMessage(response, 'ai'); |
| } |
| |
| function escapeHtml(text) { |
| const div = document.createElement('div'); |
| div.textContent = text; |
| return div.innerHTML; |
| } |
| |
| |
| const canvas = document.getElementById('mainCanvas'); |
| const ctx = canvas.getContext('2d'); |
| let currentTool = 'select'; |
| let isDrawing = false; |
| let startX, startY; |
| let zoom = 1; |
| |
| function setTool(tool) { |
| currentTool = tool; |
| document.querySelectorAll('[id^="tool-"]').forEach(btn => { |
| btn.classList.remove('text-blue-400'); |
| btn.classList.add('text-slate-400'); |
| }); |
| document.getElementById('tool-' + tool).classList.remove('text-slate-400'); |
| document.getElementById('tool-' + tool).classList.add('text-blue-400'); |
| |
| canvas.style.cursor = tool === 'select' ? 'default' : 'crosshair'; |
| } |
| |
| function clearCanvas() { |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| } |
| |
| function downloadCanvas() { |
| const link = document.createElement('a'); |
| link.download = 'phantom-canvas-' + Date.now() + '.png'; |
| link.href = canvas.toDataURL(); |
| link.click(); |
| } |
| |
| function zoomCanvas(delta) { |
| zoom = Math.max(0.5, Math.min(2, zoom + delta)); |
| document.getElementById('zoomLevel').textContent = Math.round(zoom * 100) + '%'; |
| canvas.style.transform = `scale(${zoom})`; |
| } |
| |
| |
| canvas.addEventListener('mousedown', (e) => { |
| if (currentTool === 'select') return; |
| |
| isDrawing = true; |
| const rect = canvas.getBoundingClientRect(); |
| startX = (e.clientX - rect.left) / zoom; |
| startY = (e.clientY - rect.top) / zoom; |
| |
| ctx.beginPath(); |
| ctx.strokeStyle = document.documentElement.classList.contains('dark') ? '#60a5fa' : '#2563eb'; |
| ctx.lineWidth = 2; |
| ctx.fillStyle = document.documentElement.classList.contains('dark') ? 'rgba(96, 165, 250, 0.1)' : 'rgba(37, 99, 235, 0.1)'; |
| }); |
| |
| canvas.addEventListener('mousemove', (e) => { |
| if (!isDrawing) return; |
| |
| const rect = canvas.getBoundingClientRect(); |
| const x = (e.clientX - rect.left) / zoom; |
| const y = (e.clientY - rect.top) / zoom; |
| |
| if (currentTool === 'pen') { |
| ctx.lineTo(x, y); |
| ctx.stroke(); |
| } |
| }); |
| |
| canvas.addEventListener('mouseup', (e) => { |
| if (!isDrawing) return; |
| isDrawing = false; |
| |
| const rect = canvas.getBoundingClientRect(); |
| const x = (e.clientX - rect.left) / zoom; |
| const y = (e.clientY - rect.top) / zoom; |
| |
| if (currentTool === 'rectangle') { |
| const w = x - startX; |
| const h = y - startY; |
| ctx.fillRect(startX, startY, w, h); |
| ctx.strokeRect(startX, startY, w, h); |
| } else if (currentTool === 'circle') { |
| const radius = Math.sqrt(Math.pow(x - startX, 2) + Math.pow(y - startY, 2)); |
| ctx.beginPath(); |
| ctx.arc(startX, startY, radius, 0, 2 * Math.PI); |
| ctx.fill(); |
| ctx.stroke(); |
| } |
| |
| ctx.closePath(); |
| }); |
| |
| |
| function initCanvas() { |
| if (document.documentElement.classList.contains('dark')) { |
| canvas.style.backgroundColor = '#0f172a'; |
| } else { |
| canvas.style.backgroundColor = '#ffffff'; |
| } |
| } |
| |
| initCanvas(); |
| </script> |
| </body> |
| </html> |