Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <!-- ============================================================ | |
| META TAGS & PAGE CONFIGURATION | |
| These tags control how the page is displayed and behaves | |
| across different devices, especially mobile. | |
| ============================================================ --> | |
| <!-- Character encoding: UTF-8 ensures proper display of international | |
| characters (accents, emoji, non-Latin scripts). --> | |
| <meta charset="UTF-8"> | |
| <!-- VIEWPORT: Critical for responsive design on mobile devices. | |
| - width=device-width: Match the screen width of the device | |
| - initial-scale=1.0: No zoom on load (1:1 pixel ratio) | |
| - maximum-scale=1.0, user-scalable=no: Prevents pinch-zoom | |
| (useful for app-like experiences where zoom would break layout) | |
| - viewport-fit=cover: Extends content into the "safe area" on | |
| iOS devices with notches (iPhone X+). Without this, content | |
| would stop at the notch edges, leaving awkward gaps. --> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> | |
| <!-- APPLE-MOBILE-WEB-APP-CAPABLE: When "Add to Home Screen" is used | |
| on iOS, this makes the page open in standalone mode (no Safari | |
| UI bars). The page looks and feels like a native app. --> | |
| <meta name="apple-mobile-web-app-capable" content="yes"> | |
| <!-- APPLE-MOBILE-WEB-APP-STATUS-BAR-STYLE: Controls the iOS status | |
| bar appearance in standalone mode. "black-translucent" makes | |
| the status bar transparent so content can extend underneath. --> | |
| <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> | |
| <!-- THEME-COLOR: Sets the browser chrome color (address bar on | |
| mobile Chrome, status bar on Android). Creates a cohesive | |
| look when the page loads. --> | |
| <meta name="theme-color" content="#050510"> | |
| <title>R.A.D.H.A</title> | |
| <!-- GOOGLE FONTS: Loads Poppins with multiple weights (300-700). | |
| display=swap prevents invisible text while font loads (FOUT | |
| instead of FOIT). The font gives the UI a modern, clean look. --> | |
| <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="style.css"> | |
| </head> | |
| <body> | |
| <!-- ============================================================ | |
| APP LAYOUT STRUCTURE (Single-Page, Vanilla HTML) | |
| This is a single-page application with NO framework (React, | |
| Vue, etc.). Everything is plain HTML + CSS + JS. The layout | |
| follows a vertical stack: orb (background) -> header -> | |
| chat area -> input bar. | |
| ============================================================ --> | |
| <div class="app"> | |
| <!-- ORB-CONTAINER: A full-screen WebGL canvas that renders an | |
| animated 3D orb as the background. It sits behind all other | |
| content. The OrbRenderer class (from orb.js) initializes | |
| and animates this. It's purely decorative—no interaction. --> | |
| <div id="orb-container"></div> | |
| <!-- ============================================================ | |
| HEADER | |
| Contains: logo/tagline, mode switch (General vs Realtime), | |
| connection status badge, and new chat button. | |
| ============================================================ --> | |
| <header class="header glass-panel"> | |
| <div class="header-left"> | |
| <h1 class="logo">R.A.D.H.A</h1> | |
| <span class="tagline">Responsive And Deeply Human Assistant</span> | |
| </div> | |
| <div class="header-center"> | |
| <!-- MODE SWITCH: Toggle between "General" (text chat) and | |
| "Realtime" (voice/streaming). The .mode-slider div | |
| slides left/right via JavaScript to indicate the | |
| active mode. Both buttons share the same container | |
| so the slider can animate between them. --> | |
| <div class="mode-switch" id="mode-switch"> | |
| <div class="mode-slider" id="mode-slider"></div> | |
| <!-- SVG ICON STRUCTURE: All icons use viewBox="0 0 24 24" | |
| (24x24 coordinate system) so they scale cleanly at | |
| any size. fill="none" + stroke="currentColor" gives | |
| outline style; stroke-width, stroke-linecap, and | |
| stroke-linejoin control line appearance. --> | |
| <button class="mode-btn active" data-mode="general" id="btn-general"> | |
| <!-- Chat bubble icon: represents text/conversation mode --> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/> | |
| </svg> | |
| General | |
| </button> | |
| <button class="mode-btn" data-mode="realtime" id="btn-realtime"> | |
| <!-- Globe/globe-wave icon: represents real-time/voice mode --> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/> | |
| </svg> | |
| Realtime | |
| </button> | |
| </div> | |
| </div> | |
| <div class="header-right"> | |
| <!-- STATUS BADGE: Shows connection state (Online/Offline). | |
| The .status-dot is typically green when connected, | |
| red/gray when disconnected. Updated by script.js. --> | |
| <div class="status-badge" id="status-badge"> | |
| <span class="status-dot"></span> | |
| <span class="status-text">Online</span> | |
| </div> | |
| <!-- NEW CHAT: Clears the conversation and starts fresh. --> | |
| <!-- SEARCH RESULTS: Toggle to show Tavily live search data (Realtime mode). --> | |
| <button class="btn-icon search-results-toggle" id="search-results-toggle" title="View search results" style="display: none;"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/> | |
| </svg> | |
| </button> | |
| <button class="btn-icon new-chat-btn" id="new-chat-btn" title="New Chat"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/> | |
| </svg> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- ============================================================ | |
| CHAT AREA | |
| Scrollable region containing the conversation. Shows a | |
| welcome screen (with chips) when empty, or message bubbles | |
| when there's history. | |
| ============================================================ --> | |
| <main class="chat-area" id="chat-area"> | |
| <div class="chat-messages" id="chat-messages"> | |
| <!-- WELCOME SCREEN: Shown when there are no messages. | |
| Chips are quick-action buttons that send preset | |
| prompts when clicked. The greeting text (e.g. "Good | |
| evening") can be time-based. --> | |
| <div class="welcome-screen" id="welcome-screen"> | |
| <div class="welcome-icon"> | |
| <!-- Stacked layers icon: symbolizes AI/layers of intelligence --> | |
| <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/> | |
| </svg> | |
| </div> | |
| <h2 class="welcome-title" id="welcome-title">Good evening.</h2> | |
| <p class="welcome-sub">How may I assist you today?</p> | |
| <div class="welcome-chips"> | |
| <button class="chip" data-msg="What can you do?">What can you do?</button> | |
| <button class="chip" data-msg="Open YouTube for me">Open YouTube</button> | |
| <button class="chip" data-msg="Tell me a fun fact">Fun fact</button> | |
| <button class="chip" data-msg="Play some music">Play music</button> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- ============================================================ | |
| INPUT BAR | |
| Fixed at bottom. Contains: auto-resizing textarea, mic | |
| (voice input), TTS (text-to-speech toggle), and send. | |
| SVG icons use viewBox for scaling; stroke attributes | |
| control line style. Multiple SVGs per button allow | |
| different states (e.g. mic on vs off). | |
| ============================================================ --> | |
| <footer class="input-bar glass-panel"> | |
| <div class="input-wrapper"> | |
| <textarea id="message-input" | |
| placeholder="Ask Radha anything..." | |
| rows="1" | |
| maxlength="32000"></textarea> | |
| <div class="input-actions"> | |
| <!-- MIC BUTTON: Two SVG states. .mic-icon = outline | |
| (idle). .mic-icon-active = filled square (recording). | |
| CSS/JS toggles visibility based on state. --> | |
| <button class="action-btn mic-btn" id="mic-btn" title="Voice input"> | |
| <!-- Microphone outline: mic body + stand --> | |
| <svg class="mic-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/> | |
| </svg> | |
| <!-- Filled square: "stop recording" / active state --> | |
| <svg class="mic-icon-active" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> | |
| <rect x="4" y="4" width="16" height="16" rx="3"/> | |
| </svg> | |
| </button> | |
| <!-- TTS BUTTON: Text-to-speech. .tts-icon-off = speaker | |
| with X (disabled). .tts-icon-on = speaker with | |
| sound waves (enabled). --> | |
| <button class="action-btn tts-btn" id="tts-btn" title="Text to Speech"> | |
| <!-- Speaker with X: TTS off --> | |
| <svg class="tts-icon-off" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/> | |
| <line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/> | |
| </svg> | |
| <!-- Speaker with sound waves: TTS on --> | |
| <svg class="tts-icon-on" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/> | |
| <path d="M15.54 8.46a5 5 0 0 1 0 7.07"/> | |
| <path d="M19.07 4.93a10 10 0 0 1 0 14.14"/> | |
| </svg> | |
| </button> | |
| <!-- SEND BUTTON: Paper plane / send icon --> | |
| <button class="action-btn send-btn" id="send-btn" title="Send message"> | |
| <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"/><polygon points="22 2 15 22 11 13 2 9 22 2"/> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="input-meta"> | |
| <span class="mode-label" id="mode-label">General Mode</span> | |
| <span class="char-count" id="char-count"></span> | |
| </div> | |
| </footer> | |
| <!-- ============================================================ | |
| SEARCH RESULTS WIDGET (Realtime mode) | |
| Fixed panel on the right showing Tavily search data: query, | |
| AI-synthesized answer, and source list. Toggle via header button. | |
| ============================================================ --> | |
| <aside class="search-results-widget glass-panel" id="search-results-widget" aria-hidden="true"> | |
| <div class="search-results-header"> | |
| <h3 class="search-results-title">Live search</h3> | |
| <button class="search-results-close" id="search-results-close" title="Close" aria-label="Close search results"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> | |
| </button> | |
| </div> | |
| <div class="search-results-query" id="search-results-query"></div> | |
| <div class="search-results-answer" id="search-results-answer"></div> | |
| <div class="search-results-list" id="search-results-list"></div> | |
| </aside> | |
| </div> | |
| <!-- ============================================================ | |
| SCRIPT LOADING ORDER | |
| orb.js MUST load first: it defines OrbRenderer and sets up | |
| the WebGL canvas in #orb-container. script.js depends on it | |
| and uses the orb for the background. Order matters because | |
| script.js may reference OrbRenderer at load time. | |
| ============================================================ --> | |
| <script src="orb.js"></script> | |
| <script src="script.js"></script> | |
| </body> | |
| </html> |