CoolFace
Apppublic

smart-models/Placebo_AI

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
index.html1162 linesDownload Raw Back to static
1<!DOCTYPE html>2<html lang="en">3 4<head>5    <meta charset="UTF-8">6    <meta name="viewport" content="width=device-width, initial-scale=1.0">7    <title>Placebo AI | Medical Intelligence</title>8    <link rel="stylesheet" href="/static/style.css">9    <!-- Favicons -->10    <link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">11    <link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">12    <link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">13    <link rel="shortcut icon" href="/static/favicon.ico">14    <link rel="manifest" href="/static/site.webmanifest">15    <style>16        #loader-container {17            position: fixed;18            top: 0;19            left: 0;20            width: 100vw;21            height: 100vh;22            background: #0a0a0a;23            z-index: 9999;24            display: flex;25            justify-content: center;26            align-items: center;27            transition: opacity 1s ease;28        }29        #main-content {30            opacity: 0;31            pointer-events: none;32            transition: opacity 1.5s ease;33        }34 35        /* Skeleton Loader Styles */36        .skeleton {37            background: linear-gradient(90deg, rgba(255,255,255,0.03) 25%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.03) 75%);38            background-size: 200% 100%;39            animation: shimmer 1.5s infinite;40            border-radius: 8px;41            margin-bottom: 12px;42        }43 44        .skeleton-text { height: 18px; width: 100%; }45        .skeleton-title { height: 28px; width: 60%; margin-bottom: 20px; }46        .skeleton-short { height: 18px; width: 40%; }47 48        @keyframes shimmer {49            0% { background-position: 200% 0; }50            100% { background-position: -200% 0; }51        }52 53        /* Side Drawer Styles */54        #side-drawer {55            position: fixed;56            top: 0;57            right: -400px;58            width: 350px;59            height: 100%;60            background: rgba(15, 23, 42, 0.95);61            backdrop-filter: blur(40px);62            border-left: 1px solid rgba(255, 255, 255, 0.1);63            z-index: 2000;64            transition: right 0.4s cubic-bezier(0.4, 0, 0.2, 1);65            padding: 30px;66            box-shadow: -10px 0 30px rgba(0,0,0,0.5);67            display: flex;68            flex-direction: column;69        }70 71        #side-drawer.open { right: 0; }72 73        .drawer-header {74            display: flex;75            justify-content: space-between;76            align-items: center;77            margin-bottom: 30px;78            border-bottom: 1px solid rgba(255,255,255,0.1);79            padding-bottom: 15px;80        }81 82        .drawer-header h2 {83            font-family: 'Montserrat', sans-serif;84            font-size: 1.2rem;85            color: #10b981;86            letter-spacing: 2px;87            text-transform: uppercase;88        }89 90        #close-drawer {91            background: none;92            border: none;93            color: white;94            font-size: 2rem;95            cursor: pointer;96            opacity: 0.5;97            transition: opacity 0.2s;98        }99 100        #close-drawer:hover { opacity: 1; }101 102        .drawer-item {103            padding: 15px;104            background: rgba(255,255,255,0.05);105            border-radius: 12px;106            margin-bottom: 12px;107            cursor: pointer;108            transition: all 0.2s;109            border: 1px solid transparent;110            font-size: 0.9rem;111            line-height: 1.4;112        }113 114        .drawer-item:hover {115            background: rgba(16, 185, 129, 0.1);116            border-color: rgba(16, 185, 129, 0.3);117            transform: translateX(-5px);118        }119 120        .drawer-item .time {121            font-size: 0.7rem;122            opacity: 0.5;123            display: block;124            margin-top: 5px;125        }126 127        .drawer-content {128            display: flex;129            flex-direction: column;130            height: 100%;131            overflow: hidden;132        }133 134        #drawer-items {135            flex: 1;136            overflow-y: auto;137            padding-right: 10px;138        }139        140        /* Custom Scrollbar for drawer */141        #drawer-items::-webkit-scrollbar {142            width: 6px;143        }144        #drawer-items::-webkit-scrollbar-track {145            background: rgba(255,255,255,0.02);146        }147        #drawer-items::-webkit-scrollbar-thumb {148            background: rgba(16, 185, 129, 0.3);149            border-radius: 3px;150        }151    </style>152</head>153 154<body>155    <!-- Spider Cursor Loader -->156    <div id="loader-container">157        <div style="position: absolute; text-align: center; pointer-events: none; z-index: 10;">158            <img src="/static/logo.png" width="80" height="80" style="filter: brightness(0) invert(1); margin-bottom: 20px; opacity: 0.8;">159            <div style="color: #10b981; font-family: 'Montserrat', sans-serif; letter-spacing: 4px; font-weight: 300; font-size: 14px;">INITIALIZING MEDICAL INTELLIGENCE...</div>160        </div>161    </div>162 163    <div id="main-content">164        <!-- Video Background Layer -->165        <div id="video-container">166            <video id="hero-video" autoplay loop muted playsinline>167                <source src="/static/bg_video.mp4" type="video/mp4">168            </video>169        </div>170 171        <!-- UI Content Layer -->172        <div class="content-layer">173            <nav>174                <div class="logo-area" style="display: flex; align-items: center; gap: 12px;">175                    <img src="/static/logo.png" width="32" height="32" alt="Logo" style="filter: brightness(0) invert(1);">176                    <a href="#" class="logo">Placebo AI</a>177                </div>178                <div class="menu-items">179                    <a href="/" class="menu-item">Home</a>180                    <a href="/architecture" class="menu-item">Architecture</a>181                    <a href="/capabilities" class="menu-item">Capabilities</a>182                    <a href="/docs" class="menu-item">Docs</a>183                    <a href="/about" class="menu-item">About</a>184                </div>185                <div class="nav-buttons" id="nav-auth-buttons">186                    <button class="btn-signup" id="signup-btn">Sign Up</button>187                    <button class="btn-login" id="login-btn">Log In</button>188                </div>189                <div class="nav-buttons" id="nav-user-buttons" style="display: none; align-items: center; gap: 16px;">190                    <span id="user-display-email" style="font-size: 13px; font-weight: 500; opacity: 0.85; color: var(--primary);">doctor@placebo.ai</span>191                    <button class="btn-login" id="logout-btn" style="width: auto; padding: 0 16px;">Log Out</button>192                </div>193            </nav>194 195            <main class="hero-content">196                <div class="badge-container">197                    <div class="badge-dark">198                        <svg class="icon" viewBox="0 0 24 24" fill="currentColor">199                            <path200                                d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />201                        </svg>202                        <span>Placebo AI</span>203                    </div>204                    <span class="badge-text">AI Medical Intelligence</span>205                </div>206 207                <h1>Next-Gen Medical AI</h1>208                <p class="subtitle">Transforms complex medical textbooks into accurate, citation-backed insights</p>209 210                <!-- Search / Chat Input Box -->211                <div class="search-container">212                    <div class="search-top">213                        <div class="credit-info">214                            <span>500/500 credits</span>215                            <button class="btn-upgrade">Upgrade</button>216                        </div>217                        <div class="ai-powered">218                            <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">219                                <path220                                    d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" />221                                <path d="M5 3v4" />222                                <path d="M19 17v4" />223                                <path d="M3 5h4" />224                                <path d="M17 19h4" />225                            </svg>226                            <span>Powered by Trinetra Labs</span>227                        </div>228                    </div>229 230                    <!-- Model Selector Toggle -->231                    <div class="model-selector">232                        <button class="model-btn active" data-mode="unified">233                            <span class="indicator-dot"></span>234                            Unified Library235                        </button>236                        <button class="model-btn" data-mode="mbbs">237                            <span class="indicator-dot"></span>238                            Dx(MBBS)239                        </button>240                        <button class="model-btn" data-mode="pharmacy">241                            <span class="indicator-dot"></span>242                            Rx(Pharmacy)243                        </button>244                    </div>245 246                    <div class="input-wrapper">247                        <input type="text" id="chat-input" placeholder="Type question..." autocomplete="off">248                        <button class="btn-submit" id="send-btn" aria-label="Send clinical query">249                            <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">250                                <path d="m5 12 7-7 7 7" />251                                <path d="M12 19V5" />252                            </svg>253                        </button>254                    </div>255 256                    <div class="search-bottom">257                        <div class="action-buttons">258                            <button class="action-btn" id="history-btn">259                                <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">260                                    <circle cx="12" cy="12" r="10" />261                                    <path d="M12 6v6l4 2" />262                                </svg>263                                History264                            </button>265                            <button class="action-btn" id="clear-btn">266                                <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">267                                    <path d="M3 6h18" />268                                    <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />269                                    <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />270                                </svg>271                                Clear272                            </button>273                            <button class="action-btn" id="prompts-btn">274                                <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">275                                    <circle cx="11" cy="11" r="8" />276                                    <path d="m21 21-4.3-4.3" />277                                </svg>278                                Prompts279                            </button>280                        </div>281                        <span class="char-counter" id="char-count">0/3,000</span>282                    </div>283                </div>284 285                <div class="app-disclaimer" style="text-align: center; font-size: 11px; color: rgba(255, 255, 255, 0.5); margin-top: 16px; font-family: 'Montserrat', sans-serif; max-width: 800px; margin-left: auto; margin-right: auto; line-height: 1.4;">286                    Disclaimer: All books used in this application belong to their respective authors and publishers. The author, publisher, and copyright owner reserve all rights. No ownership of book content is claimed by this application.287                </div>288 289                <!-- Response Panel -->290                <div id="response-panel">291                    <div class="response-content" id="response-text"></div>292                    <div class="source-container" id="source-list"></div>293                    294                    <!-- Feedback Loop -->295                    <div class="response-feedback" id="response-feedback" style="display: none;">296                        <span class="feedback-label">Was this helpful?</span>297                        <button class="feedback-btn" id="thumbs-up-btn" aria-label="Thumbs up: Helpful answer">298                            <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">299                                <path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" />300                            </svg>301                        </button>302                        <button class="feedback-btn" id="thumbs-down-btn" aria-label="Thumbs down: Unhelpful answer">303                            <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">304                                <path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm12-3h3a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-3" />305                            </svg>306                        </button>307                    </div>308 309                    <!-- Disclaimer -->310                    <div class="response-disclaimer">311                        <svg class="icon disclaimer-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">312                            <circle cx="12" cy="12" r="10" />313                            <line x1="12" y1="8" x2="12" y2="12" />314                            <line x1="12" y1="16" x2="12.01" y2="16" />315                        </svg>316                        <span>Placebo AI is a clinical retrieval system. Synthesized knowledge should be clinically verified.</span>317                    </div>318                </div>319            </main>320        </div>321    </div>322 323    <!-- Page Viewer Modal -->324    <div id="page-modal">325        <div class="modal-container">326            <div class="modal-header">327                <div class="modal-title" id="modal-title">Page Preview</div>328                <button class="close-modal" id="close-modal" aria-label="Close page preview modal">&times;</button>329            </div>330            <div class="modal-body">331                <img id="page-preview-img" src="" alt="Page Preview">332            </div>333        </div>334    </div>335 336    <!-- Authentication Modal Overlay -->337    <div id="auth-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); backdrop-filter: blur(20px); z-index: 3000; display: none; align-items: center; justify-content: center; padding: 20px;">338        <div class="modal-container" style="max-width: 400px; border: 1px solid rgba(16, 185, 129, 0.2); box-shadow: 0 0 30px rgba(16, 185, 129, 0.1);">339            <div class="modal-header">340                <div class="modal-title" id="auth-modal-title" style="font-family: 'Surgena', serif; font-size: 24px; color: var(--white);">Authentication</div>341                <button class="close-modal" id="close-auth-modal" aria-label="Close authentication modal" style="font-size: 28px;">&times;</button>342            </div>343            <div class="modal-body" style="background: transparent; display: flex; flex-direction: column; gap: 16px; padding: 24px;">344                <!-- Google OAuth Sign In Button -->345                <button id="google-auth-btn" style="width: 100%; height: 48px; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; display: flex; align-items: center; justify-content: center; gap: 12px; color: var(--white); font-family: 'Montserrat', sans-serif; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s; box-shadow: 0 4px 12px rgba(0,0,0,0.1);" onmouseover="this.style.background='rgba(255,255,255,0.12)'; this.style.borderColor='rgba(16, 185, 129, 0.5)'" onmouseout="this.style.background='rgba(255,255,255,0.06)'; this.style.borderColor='rgba(255,255,255,0.15)'">346                    <svg width="20" height="20" viewBox="0 0 24 24" style="flex-shrink: 0;">347                        <path fill="#EA4335" d="M12 5.04c1.66 0 3.2.57 4.38 1.69l3.27-3.27C17.67 1.48 14.97 1 12 1 7.35 1 3.4 3.65 1.56 7.5l3.86 3C6.34 7.55 8.94 5.04 12 5.04z"/>348                        <path fill="#4285F4" d="M23.49 12.27c0-.81-.07-1.59-.2-2.34H12v4.43h6.44c-.28 1.47-1.11 2.71-2.36 3.55l3.65 2.84c2.13-1.97 3.36-4.87 3.36-8.48z"/>349                        <path fill="#FBBC05" d="M5.42 14.5c-.25-.74-.39-1.53-.39-2.35s.14-1.61.39-2.35L1.56 6.8C.56 8.79 0 11.01 0 12.35s.56 3.56 1.56 5.55l3.86-3z"/>350                        <path fill="#34A853" d="M12 23c3.24 0 5.97-1.07 7.96-2.91l-3.65-2.84c-1.01.68-2.31 1.09-3.96 1.09-3.06 0-5.66-2.51-6.58-5.46l-3.86 3C3.4 20.35 7.35 23 12 23z"/>351                    </svg>352                    Continue with Google353                </button>354 355                <div style="display: flex; align-items: center; justify-content: center; width: 100%; margin: 4px 0;">356                    <div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>357                    <span style="font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.5px; opacity: 0.5; color: var(--white); padding: 0 12px;">or email verification</span>358                    <div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>359                </div>360 361                <!-- OTP Step 1: Send Verification Code -->362                <div id="otp-step-1" style="display: flex; flex-direction: column; gap: 16px; width: 100%;">363                    <div style="display: flex; flex-direction: column; gap: 6px; width: 100%;">364                        <label style="font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; opacity: 0.8; color: var(--white); text-align: left;">Email Address</label>365                        <input type="email" id="auth-email" placeholder="doctor@placebo.ai" style="width: 100%; padding: 12px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: white; font-family: 'Montserrat', sans-serif; outline: none; transition: border-color 0.3s;" onfocus="this.style.borderColor='#10b981'" onblur="this.style.borderColor='rgba(255,255,255,0.1)'">366                    </div>367                    <button class="btn-signup" id="auth-send-otp-btn" style="width: 100%; height: 46px; font-size: 14px; margin-top: 4px;">Send Verification Code</button>368                </div>369 370                <!-- OTP Step 2: Verify 8-digit Code (Initially Hidden) -->371                <div id="otp-step-2" style="display: none; flex-direction: column; gap: 16px; width: 100%;">372                    <div style="display: flex; flex-direction: column; gap: 6px; width: 100%;">373                        <label style="font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; opacity: 0.8; color: var(--white); text-align: left;">8-Digit OTP Code</label>374                        <input type="text" id="auth-otp-code" placeholder="••••••••" maxlength="8" style="width: 100%; padding: 12px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: white; font-family: 'Montserrat', sans-serif; font-size: 20px; font-weight: 700; letter-spacing: 8px; text-align: center; outline: none; transition: border-color 0.3s;" onfocus="this.style.borderColor='#10b981'" onblur="this.style.borderColor='rgba(255,255,255,0.1)'">375                    </div>376                    <button class="btn-signup" id="auth-verify-otp-btn" style="width: 100%; height: 46px; font-size: 14px; margin-top: 4px;">Verify & Log In</button>377                    <div style="text-align: center; font-size: 12px; opacity: 0.7;">378                        <a href="#" id="auth-back-to-step1" style="color: var(--primary); text-decoration: none; font-weight: 600;">Go back to enter email</a>379                    </div>380                </div>381 382                <!-- Unified Authentication Message Output -->383                <div id="auth-message" style="text-align: center; font-size: 13px; font-weight: 500; margin-top: 4px; min-height: 18px; color: var(--primary); width: 100%;"></div>384            </div>385        </div>386    </div>387 388    <!-- Profession Selection Modal Overlay -->389    <div id="profession-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); backdrop-filter: blur(20px); z-index: 4000; display: none; align-items: center; justify-content: center; padding: 20px;">390        <div class="modal-container" style="max-width: 400px; border: 1px solid rgba(16, 185, 129, 0.2); box-shadow: 0 0 30px rgba(16, 185, 129, 0.1);">391            <div class="modal-header">392                <div class="modal-title" style="font-family: 'Surgena', serif; font-size: 24px; color: var(--white);">Welcome!</div>393            </div>394            <div class="modal-body" style="background: transparent; display: flex; flex-direction: column; gap: 16px; padding: 24px; text-align: center;">395                <p style="color: var(--white); opacity: 0.8; font-family: 'Montserrat', sans-serif; font-size: 14px; margin-bottom: 8px;">What best describes you?</p>396                <button class="profession-btn" data-mode="mbbs" style="width: 100%; padding: 12px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: white; font-family: 'Montserrat', sans-serif; font-weight: 600; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='rgba(16, 185, 129, 0.2)'" onmouseout="this.style.background='rgba(255,255,255,0.05)'">MBBS Student / Doctor</button>397                <button class="profession-btn" data-mode="pharmacy" style="width: 100%; padding: 12px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: white; font-family: 'Montserrat', sans-serif; font-weight: 600; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='rgba(16, 185, 129, 0.2)'" onmouseout="this.style.background='rgba(255,255,255,0.05)'">Pharmacy Student / Pharmacist</button>398                <button class="profession-btn" data-mode="unified" style="width: 100%; padding: 12px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: white; font-family: 'Montserrat', sans-serif; font-weight: 600; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='rgba(16, 185, 129, 0.2)'" onmouseout="this.style.background='rgba(255,255,255,0.05)'">Unified (Both Domains)</button>399            </div>400        </div>401    </div>402 403    <!-- Side Drawer for History/Prompts -->404    <div id="side-drawer">405        <div class="drawer-content">406            <div class="drawer-header">407                <h2 id="drawer-title">History</h2>408                <button id="close-drawer">&times;</button>409            </div>410            <div id="drawer-items"></div>411        </div>412    </div>413 414    <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>415    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>416    <script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>417    <script src="/static/spider-loader.js"></script>418    <script>419        // --- SUPABASE CLIENT INITIALIZATION ---420        let supabaseClient = null;421        let activeUser = null;422        let activeToken = null;423 424        async function initializeSupabase() {425            try {426                const response = await fetch("/config");427                const config = await response.json();428                const SUPABASE_URL = config.supabase_url;429                const SUPABASE_ANON_KEY = config.supabase_anon_key;430 431                if (window.supabase && typeof window.supabase.createClient === 'function' && SUPABASE_URL && SUPABASE_URL !== "https://your-project-id.supabase.co") {432                    // Wipe any old local storage sessions lingering from before this feature433                    for (let key in localStorage) {434                        if (key.startsWith('sb-') && key.endsWith('-auth-token')) {435                            localStorage.removeItem(key);436                        }437                    }438                    439                    supabaseClient = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {440                        auth: {441                            persistSession: false // Forces logout on every page refresh442                        }443                    });444                    console.log("Supabase Client initialized with ephemeral sessions. Testing connectivity...");445                    446                    // Connection ping test with 2-second timeout447                    const pingPromise = supabaseClient.auth.getSession();448                    const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Supabase connection timeout")), 2000));449                    450                    await Promise.race([pingPromise, timeoutPromise]);451                    console.log("Supabase connectivity verified successfully.");452                    453                    supabaseClient.auth.onAuthStateChange((event, session) => {454                        console.log("Auth State Changed Event:", event);455                        updateAuthState();456                    });457                } else {458                    console.warn("Supabase configuration is using placeholder credentials or offline.");459                }460            } catch (e) {461                console.warn("Failed to verify Supabase connectivity or load config.", e);462                supabaseClient = null;463            }464            // Sync current authentication state on load465            await updateAuthState();466        }467 468        async function updateAuthState() {469            const authButtons = document.getElementById('nav-auth-buttons');470            const userButtons = document.getElementById('nav-user-buttons');471            const displayEmail = document.getElementById('user-display-email');472            473            if (supabaseClient) {474                try {475                    const { data: { session } } = await supabaseClient.auth.getSession();476                    if (session) {477                        activeUser = session.user;478                        activeToken = session.access_token;479                    } else {480                        activeUser = null;481                        activeToken = null;482                    }483                } catch (err) {484                    console.error("Error fetching Supabase session:", err);485                }486            }487            488            if (activeUser) {489                authButtons.style.display = 'none';490                userButtons.style.display = 'flex';491                displayEmail.innerText = activeUser.email;492                493                let userDefaultMode = activeUser.user_metadata?.defaultMode;494                if (!userDefaultMode) {495                    document.getElementById('profession-modal').style.display = 'flex';496                } else {497                    activeMode = userDefaultMode;498                    updateModeButtons(activeMode);499                }500                501                // Dynamically update user limits502                const creditText = document.querySelector('.credit-info span');503                if (creditText) {504                    let userCredits = activeUser.user_metadata?.credits_remaining !== undefined ? activeUser.user_metadata.credits_remaining : 500;505                    if (userCredits > 500) userCredits = 500; // Cap grandfathered users at 500506                    creditText.innerText = `${userCredits}/500 credits`;507                }508            } else {509                authButtons.style.display = 'flex';510                userButtons.style.display = 'none';511                activeToken = null;512                const creditText = document.querySelector('.credit-info span');513                if (creditText) {514                    creditText.innerText = "Log in to check credits";515                }516            }517        }518 519        // Initialize state check520        window.addEventListener('DOMContentLoaded', () => {521            initializeSupabase();522        });523 524        const chatForm = document.getElementById('chat-form');525        const video = document.getElementById('hero-video');526        let opacity = 1;527 528        function setOpacity(val) {529            opacity = val;530            video.style.opacity = opacity;531        }532 533        if (video.readyState >= 1) {534            video.play();535            setOpacity(1);536        } else {537            video.addEventListener('loadedmetadata', () => {538                video.play();539                setOpacity(1);540            });541        }542 543        setTimeout(() => setOpacity(1), 2000);544 545        const chatInput = document.getElementById('chat-input');546        const sendBtn = document.getElementById('send-btn');547        const charCount = document.getElementById('char-count');548        const responsePanel = document.getElementById('response-panel');549        const responseText = document.getElementById('response-text');550        const sourceList = document.getElementById('source-list');551        const pageModal = document.getElementById('page-modal');552        const modalImg = document.getElementById('page-preview-img');553        const modalTitle = document.getElementById('modal-title');554        const closeModal = document.getElementById('close-modal');555        const clearBtn = document.getElementById('clear-btn');556 557        let activeMode = 'unified';558        559        function updateModeButtons(mode) {560            const buttons = document.querySelectorAll('.model-btn');561            buttons.forEach(b => {562                b.classList.remove('active');563                if (b.getAttribute('data-mode') === mode || (mode === 'unified' && b.getAttribute('data-mode') === 'all')) {564                    b.classList.add('active');565                }566            });567        }568 569        document.querySelectorAll('.profession-btn').forEach(btn => {570            btn.onclick = async () => {571                const mode = btn.getAttribute('data-mode');572                if (supabaseClient && activeUser) {573                    btn.innerText = "Saving...";574                    const { data, error } = await supabaseClient.auth.updateUser({575                        data: { defaultMode: mode }576                    });577                    if (!error) {578                        activeUser = data.user;579                        activeMode = mode;580                        updateModeButtons(mode);581                        document.getElementById('profession-modal').style.display = 'none';582                        583                        // Reset button text in case they log out and it re-shows584                        btn.innerText = mode === "mbbs" ? "MBBS Student / Doctor" : 585                                      (mode === "pharmacy" ? "Pharmacy Student / Pharmacist" : "Unified (Both Domains)");586                    }587                }588            };589        });590 591        const modelButtons = document.querySelectorAll('.model-btn');592        modelButtons.forEach(btn => {593            btn.addEventListener('click', () => {594                modelButtons.forEach(b => b.classList.remove('active'));595                btn.classList.add('active');596                activeMode = btn.getAttribute('data-mode');597            });598        });599 600        if (clearBtn) {601            clearBtn.addEventListener('click', (e) => {602                e.preventDefault();603                responseText.innerHTML = '';604                sourceList.innerHTML = '';605                responsePanel.style.display = 'none';606                chatInput.value = '';607                charCount.innerText = '0/3,000';608            });609        }610 611        chatInput.addEventListener('input', () => {612            charCount.innerText = `${chatInput.value.length}/3,000`;613        });614 615        const historyBtn = document.getElementById('history-btn');616        const promptsBtn = document.getElementById('prompts-btn');617        const sideDrawer = document.getElementById('side-drawer');618        const closeDrawer = document.getElementById('close-drawer');619        const drawerTitle = document.getElementById('drawer-title');620        const drawerItems = document.getElementById('drawer-items');621 622        const PREDEFINED_PROMPTS = [623            "Explain the clinical presentation, diagnosis, and surgical management of acute appendicitis.",624            "What are the histological features of a myocardial infarction across different time frames?",625            "Explain the mechanism of action, pharmacokinetics, and adverse effects of Aspirin.",626            "What is the principle behind Non-aqueous Titrations and which indicators are used?",627            "Explain Type II Diabetes Mellitus, including its pathophysiology and the pharmacological management using Metformin.",628            "Describe the physiological pathway of the Renin-Angiotensin-Aldosterone System (RAAS)."629        ];630 631        function saveToHistory(query, answer = '', sources = []) {632            const historyKey = activeUser ? `chat_history_${activeUser.id}` : 'chat_history_guest';633            let history = JSON.parse(localStorage.getItem(historyKey) || '[]');634            // Find if query exists, remove it (to bring to top)635            history = history.filter(h => h.query !== query);636            history.unshift({ 637                query, 638                answer, 639                sources,640                time: new Date().toLocaleString() 641            });642            history = history.slice(0, 20); // Keep last 20643            localStorage.setItem(historyKey, JSON.stringify(history));644        }645 646        function displayStoredResult(item) {647            const queryHTML = `<div style="font-size: 16px; font-weight: 500; color: white; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid rgba(255,255,255,0.1);"><span style="opacity: 0.5; font-size: 12px; display: block; margin-bottom: 4px; text-transform: uppercase; letter-spacing: 1px;">You Asked:</span>${item.query}</div>`;648            649            let galleryHTML = '<div class="source-images-gallery" style="display: flex; gap: 10px; margin-top: 15px; overflow-x: auto; padding-bottom: 10px;">';650            sourceList.innerHTML = '';651            item.sources.forEach(src => {652                const tag = document.createElement('span');653                tag.className = 'source-tag';654                tag.innerText = `${src.book_name} (Pg ${src.page_number})`;655                tag.onclick = () => {656                    modalTitle.innerText = `${src.book_name} - Page ${src.page_number}`;657                    modalImg.src = `/page_image?path=${encodeURIComponent(src.image_path)}`;658                    pageModal.style.display = 'flex';659                };660                sourceList.appendChild(tag);661                662                galleryHTML += `663                    <div style="flex: 0 0 auto; width: 250px; border: 1px solid rgba(16, 185, 129, 0.3); border-radius: 8px; overflow: hidden; background: rgba(0,0,0,0.2); cursor: pointer;" onclick="document.getElementById('modal-title').innerText='${src.book_name} - Page ${src.page_number}'; document.getElementById('modal-img').src='/page_image?path=${encodeURIComponent(src.image_path)}'; document.getElementById('page-modal').style.display='flex';">664                        <div style="font-size: 10px; color: var(--primary); padding: 5px 8px; border-bottom: 1px solid rgba(16, 185, 129, 0.2); font-weight: 600; text-transform: uppercase;">Exact Page Reference</div>665                        <img src="/page_image?path=${encodeURIComponent(src.image_path)}" style="width: 100%; height: auto; object-fit: contain; display: block;" alt="Reference Page">666                    </div>667                `;668            });669            galleryHTML += '</div>';670            671            responseText.innerHTML = queryHTML + DOMPurify.sanitize(marked.parse(item.answer)) + (item.sources.length > 0 ? galleryHTML : "");672            673            responsePanel.style.display = 'flex';674            chatInput.value = item.query;675            sideDrawer.classList.remove('open');676            window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });677        }678 679        function openDrawer(type) {680            drawerItems.innerHTML = '';681            if (type === 'history') {682                drawerTitle.innerText = 'History';683                const historyKey = activeUser ? `chat_history_${activeUser.id}` : 'chat_history_guest';684                const history = JSON.parse(localStorage.getItem(historyKey) || '[]');685                if (history.length === 0) {686                    drawerItems.innerHTML = '<div style="opacity: 0.5; text-align: center; margin-top: 20px;">No history yet.</div>';687                }688                history.forEach(item => {689                    const el = document.createElement('div');690                    el.className = 'drawer-item';691                    el.innerHTML = `${item.query} <span class="time">${item.time}</span>`;692                    el.onclick = () => displayStoredResult(item);693                    drawerItems.appendChild(el);694                });695            } else {696                drawerTitle.innerText = 'Prompts';697                PREDEFINED_PROMPTS.forEach(prompt => {698                    const el = document.createElement('div');699                    el.className = 'drawer-item';700                    el.innerText = prompt;701                    el.onclick = () => {702                        chatInput.value = prompt;703                        sideDrawer.classList.remove('open');704                        handleSend();705                    };706                    drawerItems.appendChild(el);707                });708            }709            sideDrawer.classList.add('open');710        }711 712        historyBtn.onclick = () => openDrawer('history');713        promptsBtn.onclick = () => openDrawer('prompts');714        closeDrawer.onclick = () => sideDrawer.classList.remove('open');715 716        // --- AUTH OVERLAY CONTROLLERS ---717        const authModal = document.getElementById('auth-modal');718        const loginBtn = document.getElementById('login-btn');719        const signupBtn = document.getElementById('signup-btn');720        const closeAuthModal = document.getElementById('close-auth-modal');721        const authModalTitle = document.getElementById('auth-modal-title');722        723        const googleAuthBtn = document.getElementById('google-auth-btn');724        725        const otpStep1 = document.getElementById('otp-step-1');726        const authEmail = document.getElementById('auth-email');727        const authSendOtpBtn = document.getElementById('auth-send-otp-btn');728        729        const otpStep2 = document.getElementById('otp-step-2');730        const authOtpCode = document.getElementById('auth-otp-code');731        const authVerifyOtpBtn = document.getElementById('auth-verify-otp-btn');732        const authBackToStep1 = document.getElementById('auth-back-to-step1');733        734        const authMessage = document.getElementById('auth-message');735        const logoutBtn = document.getElementById('logout-btn');736 737        function resetAuthModal() {738            authMessage.innerText = '';739            authEmail.value = '';740            authOtpCode.value = '';741            otpStep1.style.display = 'flex';742            otpStep2.style.display = 'none';743        }744 745        function showAuthModal() {746            resetAuthModal();747            authModal.style.display = 'flex';748            if (!supabaseClient) {749                authMessage.style.color = '#eab308'; // Warning gold/yellow750                authMessage.innerText = 'Supabase server is offline (Error 521). Sandbox simulation mode active.';751            }752        }753 754        loginBtn.onclick = () => showAuthModal();755        signupBtn.onclick = () => showAuthModal();756        closeAuthModal.onclick = () => authModal.style.display = 'none';757        758        // Close auth modal when clicking outside content box759        authModal.onclick = (e) => {760            if (e.target === authModal) {761                authModal.style.display = 'none';762            }763        };764 765        // Go back to Step 1766        authBackToStep1.onclick = (e) => {767            e.preventDefault();768            authMessage.innerText = '';769            authOtpCode.value = '';770            otpStep1.style.display = 'flex';771            otpStep2.style.display = 'none';772        };773 774        // --- DISPOSABLE EMAIL CHECK ---775        function isDisposableEmail(email) {776            const disposableDomains = [777                'mailinator.com', 'tempmail.com', 'temp-mail.org', '10minutemail.com', 778                'yopmail.com', 'guerrillamail.com', 'dispostable.com', 'trashmail.com', 779                'getairmail.com', 'sharklasers.com', 'guerrillamailblock.com', 780                'guerrillamail.net', 'guerrillamail.org', 'guerrillamail.biz', 781                'tempmailo.com', 'generator.email', 'maildrop.cc', 'tempmail.dev',782                'tempmail.net', 'disposable.com', 'fakeinbox.com', 'disposablemail.com'783            ];784            const parts = email.split('@');785            if (parts.length < 2) return true;786            const domain = parts[1].toLowerCase().trim();787            return disposableDomains.includes(domain);788        }789 790        // --- GOOGLE OAUTH LOGIN TRIGGER ---791        googleAuthBtn.onclick = async () => {792            if (!supabaseClient) {793                authMessage.style.color = '#ef4444';794                authMessage.innerText = 'Authentication service is offline. Mock login is disabled.';795                return;796            }797 798            authMessage.style.color = '#10b981';799            authMessage.innerText = 'Redirecting to Google...';800            801            try {802                const { error } = await supabaseClient.auth.signInWithOAuth({803                    provider: 'google',804                    options: {805                        redirectTo: window.location.origin806                    }807                });808                if (error) throw error;809            } catch (err) {810                authMessage.style.color = '#ef4444';811                authMessage.innerText = err.message || 'OAuth redirection failed.';812            }813        };814 815        // --- PASSWORDLESS SEND EMAIL OTP TRIGGER ---816        authSendOtpBtn.onclick = async () => {817            const email = authEmail.value.trim();818            if (!email) {819                authMessage.style.color = '#ef4444';820                authMessage.innerText = 'Please enter a valid email address.';821                return;822            }823 824            if (isDisposableEmail(email)) {825                authMessage.style.color = '#ef4444';826                authMessage.innerText = 'Please use a valid personal, academic, or professional email address. Temporary email addresses are not supported.';827                return;828            }829 830            if (!supabaseClient) {831                authMessage.style.color = '#ef4444';832                authMessage.innerText = 'Authentication service is offline. Mock login is disabled.';833                return;834            }835 836            authSendOtpBtn.disabled = true;837            authMessage.style.color = '#10b981';838            authMessage.innerText = 'Sending verification code...';839 840            try {841                const { error } = await supabaseClient.auth.signInWithOtp({842                    email: email,843                    options: {844                        shouldCreateUser: true // Automatically sign up new users845                    }846                });847                if (error) throw error;848                849                authMessage.innerText = 'Verification code sent to your email!';850                setTimeout(() => {851                    authMessage.innerText = '';852                    otpStep1.style.display = 'none';853                    otpStep2.style.display = 'flex';854                    authOtpCode.focus();855                }, 1000);856            } catch (err) {857                authMessage.style.color = '#ef4444';858                authMessage.innerText = err.message || 'Failed to send OTP code.';859            } finally {860                authSendOtpBtn.disabled = false;861            }862        };863 864        // --- PASSWORDLESS VERIFY EMAIL OTP TRIGGER ---865        authVerifyOtpBtn.onclick = async () => {866            const email = authEmail.value.trim();867            const otpCode = authOtpCode.value.trim();868            if (!otpCode || otpCode.length !== 8) {869                authMessage.style.color = '#ef4444';870                authMessage.innerText = 'Please enter the 8-digit verification code.';871                return;872            }873 874            if (!supabaseClient) {875                authMessage.style.color = '#ef4444';876                authMessage.innerText = 'Authentication service is offline. Mock login is disabled.';877                return;878            }879 880            authVerifyOtpBtn.disabled = true;881            authMessage.style.color = '#10b981';882            authMessage.innerText = 'Verifying security code...';883 884            try {885                const { data, error } = await supabaseClient.auth.verifyOtp({886                    email: email,887                    token: otpCode,888                    type: 'email'889                });890                if (error) throw error;891                892                authMessage.innerText = 'Authentication successful!';893                setTimeout(() => {894                    authModal.style.display = 'none';895                    updateAuthState();896                }, 1000);897            } catch (err) {898                authMessage.style.color = '#ef4444';899                authMessage.innerText = err.message || 'Invalid or expired verification code.';900            } finally {901                authVerifyOtpBtn.disabled = false;902            }903        };904 905        logoutBtn.onclick = async () => {906            if (supabaseClient) {907                await supabaseClient.auth.signOut();908            } else {909                activeUser = null;910                activeToken = null;911                updateAuthState();912            }913        };914 915        async function handleSend() {916            if (!activeUser) {917                showAuthModal();918                return;919            }920 921            const message = chatInput.value.trim();922            if (!message) return;923 924            // Disable inputs while processing925            chatInput.disabled = true;926            sendBtn.disabled = true;927            document.querySelector('.input-wrapper').classList.add('disabled');928 929            // Hide feedback widget and reset its state930            const feedbackContainer = document.getElementById('response-feedback');931            const thumbsUpBtn = document.getElementById('thumbs-up-btn');932            const thumbsDownBtn = document.getElementById('thumbs-down-btn');933            if (feedbackContainer) feedbackContainer.style.display = 'none';934            if (thumbsUpBtn) thumbsUpBtn.classList.remove('active-up');935            if (thumbsDownBtn) thumbsDownBtn.classList.remove('active-down');936 937            saveToHistory(message);938            chatInput.value = '';939            charCount.innerText = '0/3,000';940 941            // --- CREDIT DEDUCTION LOGIC ---942            if (activeUser && supabaseClient) {943                let currentCredits = activeUser.user_metadata?.credits_remaining !== undefined ? activeUser.user_metadata.credits_remaining : 500;944                if (currentCredits > 500) currentCredits = 500; // Instantly scale legacy users down to new 500 limit945                946                if (currentCredits < 10) {947                    responsePanel.style.display = 'flex';948                    responseText.innerHTML = '<span style="color: #ef4444; padding: 20px; display: block; text-align: center;">Insufficient credits. You need at least 10 credits to perform a clinical search. Please upgrade your account.</span>';949                    chatInput.disabled = false;950                    sendBtn.disabled = false;951                    document.querySelector('.input-wrapper').classList.remove('disabled');952                    return;953                }954                955                let newCredits = Math.max(0, currentCredits - 10);956                957                // Optimistic UI update958                const creditText = document.querySelector('.credit-info span');959                if (creditText) creditText.innerText = `${newCredits}/500 credits`;960                961                // Background sync with Supabase User Metadata962                supabaseClient.auth.updateUser({963                    data: { credits_remaining: newCredits }964                }).then(({ data, error }) => {965                    if (!error && data.user) {966                        activeUser = data.user;967                    }968                });969            }970 971            responsePanel.style.display = 'flex';972            973            let modeDisplay = "Unified Library";974            if (activeMode === "mbbs") modeDisplay = "Dx(MBBS) Knowledge Base";975            if (activeMode === "pharmacy") modeDisplay = "Rx(Pharmacy) Knowledge Base";976            977            const queryHTML = `<div style="font-size: 16px; font-weight: 500; color: white; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid rgba(255,255,255,0.1);"><span style="opacity: 0.5; font-size: 12px; display: block; margin-bottom: 4px; text-transform: uppercase; letter-spacing: 1px;">You Asked:</span>${message}</div>`;978            const badgeHTML = `<div style="font-size: 11px; color: var(--primary); font-weight: 600; letter-spacing: 1px; text-transform: uppercase; margin-bottom: 12px; border-left: 2px solid var(--primary); padding-left: 8px;">Response from ${modeDisplay}</div>`;979            980            responseText.innerHTML = queryHTML + badgeHTML + `981                <div class="skeleton skeleton-title"></div>982                <div class="skeleton skeleton-text"></div>983                <div class="skeleton skeleton-text"></div>984                <div class="skeleton skeleton-short"></div>985            `;986            sourceList.innerHTML = '';987 988            try {989                const headers = { 'Content-Type': 'application/json' };990                if (activeToken) {991                    headers['Authorization'] = `Bearer ${activeToken}`;992                }993 994                const response = await fetch('/chat', {995                    method: 'POST',996                    headers: headers,997                    body: JSON.stringify({ message: message, mode: activeMode })998                });999 1000                if (!response.ok) {1001                    if (response.status === 401) {1002                        responseText.innerHTML = '<span style="color: #ef4444;">Session expired or unauthorized. Please log out and log back in.</span>';1003                        return;1004                    }1005                    throw new Error(`HTTP Error: ${response.status}`);1006                }1007 1008                const reader = response.body.getReader();1009                const decoder = new TextDecoder();1010                let fullAnswer = "";1011                let sources = [];1012                let sourceGalleryHTML = "";1013 1014                while (true) {1015                    const { done, value } = await reader.read();1016                    if (done) break;1017 1018                    const chunk = decoder.decode(value, { stream: true });1019                    const lines = chunk.split('\n');1020 1021                    for (const line of lines) {1022                        if (!line.trim()) continue;1023                        try {1024                            const json = JSON.parse(line);1025                            if (json.type === 'sources') {1026                                sources = json.data;1027                                // Create a visual gallery for the images inline1028                                sourceGalleryHTML = '<div class="source-images-gallery" style="display: flex; gap: 10px; margin-top: 15px; overflow-x: auto; padding-bottom: 10px;">';1029                                sources.forEach(src => {1030                                    // Keep the clickable tags for history1031                                    const tag = document.createElement('span');1032                                    tag.className = 'source-tag';1033                                    tag.innerText = `${src.book_name} (Pg ${src.page_number})`;1034                                    tag.onclick = () => {1035                                        modalTitle.innerText = `${src.book_name} - Page ${src.page_number}`;1036                                        modalImg.src = `/page_image?path=${encodeURIComponent(src.image_path)}`;1037                                        pageModal.style.display = 'flex';1038                                    };1039                                    sourceList.appendChild(tag);1040                                    1041                                    // Auto-embed the exact image directly in the chat1042                                    sourceGalleryHTML += `1043                                        <div style="flex: 0 0 auto; width: 250px; border: 1px solid rgba(16, 185, 129, 0.3); border-radius: 8px; overflow: hidden; background: rgba(0,0,0,0.2); cursor: pointer;" onclick="document.getElementById('modal-title').innerText='${src.book_name} - Page ${src.page_number}'; document.getElementById('modal-img').src='/page_image?path=${encodeURIComponent(src.image_path)}'; document.getElementById('page-modal').style.display='flex';">1044                                            <div style="font-size: 10px; color: var(--primary); padding: 5px 8px; border-bottom: 1px solid rgba(16, 185, 129, 0.2); font-weight: 600; text-transform: uppercase;">Exact Page Reference</div>1045                                            <img src="/page_image?path=${encodeURIComponent(src.image_path)}" style="width: 100%; height: auto; object-fit: contain; display: block;" alt="Reference Page">1046                                        </div>1047                                    `;1048                                });1049                                sourceGalleryHTML += '</div>';1050                                1051                                // Manually update the view since sources arrive last in the stream now1052                                responseText.innerHTML = queryHTML + badgeHTML + DOMPurify.sanitize(marked.parse(fullAnswer)) + sourceGalleryHTML;1053                                window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });1054                                1055                            } else if (json.type === 'start_answer') {1056                                responseText.innerHTML = queryHTML + badgeHTML + sourceGalleryHTML;1057                                fullAnswer = ""; // Reset buffer1058                            } else if (json.type === 'content') {1059                                fullAnswer += json.data;1060                                responseText.innerHTML = queryHTML + badgeHTML + DOMPurify.sanitize(marked.parse(fullAnswer)) + sourceGalleryHTML;1061                                window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });1062                            }1063                        } catch (e) { }1064                    }1065                }1066                // Save complete result to history1067                saveToHistory(message, fullAnswer, sources);1068 1069                // Revert to user's default mode if they did a temporary override1070                if (activeUser?.user_metadata?.defaultMode) {1071                    activeMode = activeUser.user_metadata.defaultMode;1072                    updateModeButtons(activeMode);1073                }1074 1075                // Show feedback widget when streaming completes successfully1076                if (feedbackContainer) feedbackContainer.style.display = 'flex';1077            } catch (error) {1078                responseText.innerHTML = '<span style="color: #ef4444;">Connection error.</span>';1079            } finally {1080                // Re-enable inputs1081                chatInput.disabled = false;1082                sendBtn.disabled = false;1083                document.querySelector('.input-wrapper').classList.remove('disabled');1084                chatInput.focus();1085            }1086        }1087 1088        const resetModal = () => { 1089            pageModal.style.display = 'none'; 1090            modalImg.dataset.scale = 1;1091            modalImg.style.transform = 'scale(1)';1092        };1093        closeModal.onclick = resetModal;1094        window.onclick = (event) => { if (event.target == pageModal) resetModal(); };1095 1096        modalImg.addEventListener('wheel', (e) => {1097            e.preventDefault();1098            const delta = e.deltaY > 0 ? 0.9 : 1.1;1099            const newScale = (modalImg.dataset.scale || 1) * delta;1100            if (newScale >= 1 && newScale <= 5) {1101                modalImg.dataset.scale = newScale;1102                modalImg.style.transform = `scale(${newScale})`;1103            }1104        });1105 1106        let isDragging = false;1107        let startX, startY, scrollLeft, scrollTop;1108 1109        const modalBody = document.querySelector('.modal-body');1110        modalBody.addEventListener('mousedown', (e) => {1111            isDragging = true;1112            startX = e.pageX - modalBody.offsetLeft;1113            startY = e.pageY - modalBody.offsetTop;1114            scrollLeft = modalBody.scrollLeft;1115            scrollTop = modalBody.scrollTop;1116        });1117 1118        modalBody.addEventListener('mouseleave', () => { isDragging = false; });1119        modalBody.addEventListener('mouseup', () => { isDragging = false; });1120 1121        modalBody.addEventListener('mousemove', (e) => {1122            if (!isDragging) return;1123            e.preventDefault();1124            const x = e.pageX - modalBody.offsetLeft;1125            const y = e.pageY - modalBody.offsetTop;1126            const walkX = (x - startX) * 2;1127            const walkY = (y - startY) * 2;1128            modalBody.scrollLeft = scrollLeft - walkX;1129            modalBody.scrollTop = scrollTop - walkY;1130        });1131 1132        // Feedback loop logic1133        const thumbsUpBtn = document.getElementById('thumbs-up-btn');1134        const thumbsDownBtn = document.getElementById('thumbs-down-btn');1135        const feedbackLabel = document.querySelector('.feedback-label');1136 1137        if (thumbsUpBtn && thumbsDownBtn) {1138            thumbsUpBtn.addEventListener('click', () => {1139                thumbsUpBtn.classList.toggle('active-up');1140                thumbsDownBtn.classList.remove('active-down');1141                if (thumbsUpBtn.classList.contains('active-up')) {1142                    feedbackLabel.innerText = "Thank you!";1143                    setTimeout(() => { feedbackLabel.innerText = "Was this helpful?"; }, 3000);1144                }1145            });1146 1147            thumbsDownBtn.addEventListener('click', () => {1148                thumbsDownBtn.classList.toggle('active-down');1149                thumbsUpBtn.classList.remove('active-up');1150                if (thumbsDownBtn.classList.contains('active-down')) {1151                    feedbackLabel.innerText = "Thank you!";1152                    setTimeout(() => { feedbackLabel.innerText = "Was this helpful?"; }, 3000);1153                }1154            });1155        }1156 1157        sendBtn.addEventListener('click', handleSend);1158        chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') handleSend(); });1159    </script>1160</body>1161 1162</html>