<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PracticeMD - Practice Management System</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
</style>
</head>
<body class="bg-gray-50 h-screen overflow-hidden">
<div class="flex h-full">
<!-- Sidebar -->
<div class="w-56 bg-white border-r border-gray-200 flex flex-col">
<div class="p-6 border-b border-gray-200">
<h1 class="text-xl font-semibold text-gray-900">PracticeMD</h1>
</div>
<nav class="p-3 flex-1">
<button onclick="showPage('dashboard')" class="nav-item active w-full flex items-center gap-3 px-3 py-2 rounded-lg mb-1 text-sm font-medium">
<span>📊</span> Dashboard
</button>
<button onclick="showPage('calendar')" class="nav-item w-full flex items-center gap-3 px-3 py-2 rounded-lg mb-1 text-sm font-medium">
<span>📅</span> Calendar
</button>
<button onclick="showPage('patients')" class="nav-item w-full flex items-center gap-3 px-3 py-2 rounded-lg mb-1 text-sm font-medium">
<span>👥</span> Patients
</button>
<button onclick="showPage('financial')" class="nav-item w-full flex items-center gap-3 px-3 py-2 rounded-lg mb-1 text-sm font-medium">
<span>💰</span> Financial
</button>
<button onclick="showPage('templates')" class="nav-item w-full flex items-center gap-3 px-3 py-2 rounded-lg mb-1 text-sm font-medium">
<span>📄</span> Templates
</button>
<button onclick="showPage('tasks')" class="nav-item w-full flex items-center gap-3 px-3 py-2 rounded-lg mb-1 text-sm font-medium">
<span>✅</span> Tasks
</button>
<button onclick="showPage('contacts')" class="nav-item w-full flex items-center gap-3 px-3 py-2 rounded-lg mb-1 text-sm font-medium">
<span>📞</span> Contacts
</button>
<button onclick="showPage('settings')" class="nav-item w-full flex items-center gap-3 px-3 py-2 rounded-lg mb-1 text-sm font-medium">
<span>⚙️</span> Settings
</button>
</nav>
</div>
<!-- Main Content -->
<div class="flex-1 flex flex-col">
<!-- Top Bar -->
<div class="bg-white border-b border-gray-200 px-6 py-4">
<div class="flex items-center gap-4">
<div class="flex-1 max-w-xl relative">
<input
type="text"
id="searchInput"
placeholder="Search patients..."
oninput="searchPatients(this.value)"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">🔍</span>
</div>
<button onclick="toggleChat()" class="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium">
<span>🎤</span> Voice Assistant
</button>
</div>
</div>
<!-- Content Pages -->
<div class="flex-1 overflow-auto p-6">
<!-- Dashboard -->
<div id="page-dashboard" class="page">
<div class="mb-6">
<h1 class="text-2xl font-semibold text-gray-900 mb-1">Welcome back, Doctor</h1>
<p class="text-gray-600" id="currentDate"></p>
</div>
<div class="grid grid-cols-4 gap-4 mb-6">
<div class="bg-white rounded-xl p-6 border border-gray-200">
<div class="text-sm text-gray-600 mb-2">Today's Appointments</div>
<div class="text-3xl font-semibold text-gray-900" id="todayCount">5</div>
<div class="text-sm text-green-600 mt-1">Ready to go</div>
</div>
<div class="bg-white rounded-xl p-6 border border-gray-200">
<div class="text-sm text-gray-600 mb-2">This Week</div>
<div class="text-3xl font-semibold text-gray-900">12</div>
<div class="text-sm text-gray-600 mt-1">Surgeries scheduled</div>
</div>
<div class="bg-white rounded-xl p-6 border border-gray-200">
<div class="text-sm text-gray-600 mb-2">Outstanding</div>
<div class="text-3xl font-semibold text-gray-900" id="unpaidCount">20</div>
<div class="text-sm text-orange-600 mt-1" id="unpaidAmount">$89,500</div>
</div>
<div class="bg-white rounded-xl p-6 border border-gray-200">
<div class="text-sm text-gray-600 mb-2">Total Patients</div>
<div class="text-3xl font-semibold text-gray-900">100</div>
<div class="text-sm text-gray-600 mt-1">In database</div>
</div>
</div>
<div class="bg-white rounded-xl border border-gray-200 p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4">Today's Schedule</h2>
<div class="space-y-3" id="todaySchedule"></div>
</div>
</div>
<!-- Patients -->
<div id="page-patients" class="page hidden">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-semibold text-gray-900">Patients (<span id="patientCount">100</span>)</h1>
<button class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium">
📸 Add Patient (Photo)
</button>
</div>
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50 border-b border-gray-200">
<tr>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-900">Name</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-900">DOB</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-900">Age</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-900">Medicare</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-900">Insurance</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-900">Status</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200" id="patientsTable"></tbody>
</table>
</div>
</div>
<!-- Financial -->
<div id="page-financial" class="page hidden">
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Financial Dashboard</h1>
<div class="grid grid-cols-4 gap-4 mb-6">
<div class="bg-white rounded-xl p-6 border border-gray-200">
<div class="text-sm text-gray-600 mb-2">Total Revenue</div>
<div class="text-3xl font-semibold text-gray-900">$567,450</div>
<div class="text-sm text-gray-600 mt-1">Nov 2025 - Jan 2026</div>
</div>
<div class="bg-white rounded-xl p-6 border border-gray-200">
<div class="text-sm text-gray-600 mb-2">Outstanding</div>
<div class="text-3xl font-semibold text-orange-600">$89,500</div>
<div class="text-sm text-gray-600 mt-1">20 invoices</div>
</div>
<div class="bg-white rounded-xl p-6 border border-gray-200">
<div class="text-sm text-gray-600 mb-2">Overdue (>30 days)</div>
<div class="text-3xl font-semibold text-red-600">$34,200</div>
<div class="text-sm text-gray-600 mt-1">8 invoices</div>
</div>
<div class="bg-white rounded-xl p-6 border border-gray-200">
<div class="text-sm text-gray-600 mb-2">Collection Rate</div>
<div class="text-3xl font-semibold text-gray-900">84%</div>
<div class="text-sm text-gray-600 mt-1">Last 90 days</div>
</div>
</div>
<div class="bg-white rounded-xl border border-gray-200 p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4">Outstanding Invoices</h2>
<div id="invoiceList"></div>
</div>
</div>
<!-- Contacts -->
<div id="page-contacts" class="page hidden">
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Professional Contacts</h1>
<div class="bg-white rounded-xl border border-gray-200 p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4">Referring Physicians</h2>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="font-semibold text-gray-900 mb-2">Dr. Kujan Nagaratnam</div>
<div class="text-sm text-gray-600 mb-3">Pre-operative Medical Assessments</div>
<div class="text-sm text-gray-700 space-y-1">
<div>📍 Norwest Private Hospital</div>
<div>📞 (02) XXXX XXXX</div>
<div>✉️ k.nagaratnam@example.com</div>
</div>
</div>
</div>
<div class="bg-white rounded-xl border border-gray-200 p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4">Device Company Representatives</h2>
<div class="grid grid-cols-2 gap-4">
<div class="p-4 bg-gray-50 rounded-lg">
<div class="font-semibold text-gray-900 mb-1">Stryker - John Smith</div>
<div class="text-sm text-gray-600 mb-2">Hip & Knee Implants</div>
<div class="text-sm text-gray-700">📱 0412 XXX XXX</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="font-semibold text-gray-900 mb-1">DePuy Synthes - Sarah Jones</div>
<div class="text-sm text-gray-600 mb-2">Trauma & Joint Replacement</div>
<div class="text-sm text-gray-700">📱 0423 XXX XXX</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="font-semibold text-gray-900 mb-1">Smith+Nephew - Michael Brown</div>
<div class="text-sm text-gray-600 mb-2">Sports Medicine & Arthroscopy</div>
<div class="text-sm text-gray-700">📱 0434 XXX XXX</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="font-semibold text-gray-900 mb-1">Zimmer Biomet - Emma Davis</div>
<div class="text-sm text-gray-600 mb-2">Trauma Plating Systems</div>
<div class="text-sm text-gray-700">📱 0445 XXX XXX</div>
</div>
</div>
</div>
</div>
<!-- Calendar -->
<div id="page-calendar" class="page hidden">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-semibold text-gray-900">Calendar</h1>
<div class="flex gap-2">
<div class="flex gap-2 mr-4">
<button onclick="setCalendarView('week')" id="viewWeekBtn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">Week</button>
<button onclick="setCalendarView('month')" id="viewMonthBtn" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">Month</button>
</div>
<button onclick="changeCalendarPeriod(-1)" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">← Previous</button>
<button onclick="changeCalendarPeriod(0)" class="px-4 py-2 bg-gray-100 rounded-lg hover:bg-gray-200">Today</button>
<button onclick="changeCalendarPeriod(1)" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">Next →</button>
</div>
</div>
<div class="bg-white rounded-xl border border-gray-200 p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4" id="calendarTitle"></h2>
<div id="calendarGrid"></div>
</div>
<div class="mt-6 bg-white rounded-xl border border-gray-200 p-6">
<h3 class="font-semibold text-gray-900 mb-3">Legend</h3>
<div class="flex gap-6 text-sm">
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-red-100 border-l-4 border-red-500 rounded"></div>
<span>Surgery</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-blue-100 border-l-4 border-blue-500 rounded"></div>
<span>Clinic</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-green-100 border-l-4 border-green-500 rounded"></div>
<span>Follow-up</span>
</div>
</div>
</div>
</div>
<div id="page-templates" class="page hidden">
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Templates Library</h1>
<div class="bg-white rounded-xl border border-gray-200 p-6 mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-gray-900">Operation Report Templates</h2>
<button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">+ New Template</button>
</div>
<div class="grid grid-cols-2 gap-4" id="templatesList">
<!-- Templates will be loaded here -->
</div>
</div>
<div class="bg-white rounded-xl border border-gray-200 p-6">
<h3 class="font-semibold text-gray-900 mb-3">Template Editor</h3>
<p class="text-sm text-gray-600 mb-4">Select a template above to view and edit</p>
<div id="templateEditor" class="hidden">
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">Template Name</label>
<input type="text" id="templateName" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">Operation Report Template</label>
<textarea id="templateContent" rows="25" class="w-full px-4 py-2 border border-gray-300 rounded-lg font-mono text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"></textarea>
</div>
<div class="flex gap-2">
<button onclick="saveTemplate()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">Save Changes</button>
<button onclick="useTemplate()" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700">Use Template for Patient</button>
</div>
</div>
</div>
</div>
<div id="page-tasks" class="page hidden">
<div class="bg-white rounded-xl border border-gray-200 p-8 text-center">
<h2 class="text-xl font-semibold text-gray-900 mb-2">Tasks</h2>
<p class="text-gray-600">Task management under development</p>
</div>
</div>
<div id="page-settings" class="page hidden">
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Settings</h1>
<div class="bg-white rounded-xl border border-gray-200 p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4">AI Configuration</h2>
<p class="text-sm text-gray-600 mb-4">
Enter your Anthropic API key to enable AI features (chat assistant, voice dictation, automation).
Get your API key from <a href="https://console.anthropic.com/" target="_blank" class="text-blue-600 hover:underline">console.anthropic.com</a>
</p>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">Anthropic API Key</label>
<div class="flex gap-2">
<input
type="password"
id="apiKeyInput"
placeholder="sk-ant-api03-..."
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
>
<button onclick="toggleApiKeyVisibility()" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">
👁️
</button>
</div>
</div>
<div class="flex gap-2">
<button onclick="saveApiKey()" class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium">
Save API Key
</button>
<button onclick="testApiKey()" class="px-6 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 font-medium">
Test Connection
</button>
</div>
<div id="apiKeyStatus" class="mt-4 hidden"></div>
</div>
<div class="bg-white rounded-xl border border-gray-200 p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4">Voice Settings</h2>
<div class="mb-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" id="voiceEnabled" checked class="w-4 h-4 text-blue-600 rounded">
<span class="text-sm text-gray-700">Enable voice input (uses browser speech recognition)</span>
</label>
</div>
<div class="mb-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" id="voiceAutoListen" class="w-4 h-4 text-blue-600 rounded">
<span class="text-sm text-gray-700">Auto-start listening when opening voice assistant</span>
</label>
</div>
</div>
<div class="bg-white rounded-xl border border-gray-200 p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4">Practice Details</h2>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Doctor Name</label>
<input type="text" placeholder="Dr. [Your Name]" class="w-full px-4 py-2 border border-gray-300 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Provider Number</label>
<input type="text" placeholder="1234567A" class="w-full px-4 py-2 border border-gray-300 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Practice Name</label>
<input type="text" value="Norwest Private Hospital" class="w-full px-4 py-2 border border-gray-300 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Phone</label>
<input type="text" placeholder="(02) XXXX XXXX" class="w-full px-4 py-2 border border-gray-300 rounded-lg">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Chat FAB -->
<button id="chatFab" onclick="toggleChat()" class="fixed bottom-6 right-6 w-14 h-14 bg-blue-600 text-white rounded-full shadow-lg hover:bg-blue-700 transition-all hover:scale-110 flex items-center justify-center text-2xl">
💬
</button>
<!-- Patient Details Modal -->
<div id="patientModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-6">
<div class="bg-white rounded-2xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<div class="sticky top-0 bg-white border-b border-gray-200 p-6 flex items-center justify-between rounded-t-2xl">
<h2 class="text-2xl font-semibold text-gray-900" id="patientModalName"></h2>
<button onclick="closePatientModal()" class="hover:bg-gray-100 rounded-lg p-2 transition-colors">
<span class="text-2xl text-gray-600">×</span>
</button>
</div>
<div class="p-6">
<!-- Patient Info Form -->
<div class="mb-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Personal Information</h3>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Full Name</label>
<input type="text" id="modal_name" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Date of Birth</label>
<input type="text" id="modal_dob" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Age</label>
<input type="text" id="modal_age" class="w-full px-4 py-2 border border-gray-300 rounded-lg bg-gray-50" readonly>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Phone</label>
<input type="text" id="modal_phone" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" id="modal_email" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Address</label>
<input type="text" id="modal_address" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="Street Address">
</div>
</div>
</div>
<div class="mb-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Emergency Contact</h3>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Emergency Contact Name</label>
<input type="text" id="modal_emergency_name" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="Contact Name">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Emergency Contact Phone</label>
<input type="text" id="modal_emergency_phone" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="Phone Number">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Relationship</label>
<input type="text" id="modal_emergency_relation" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="e.g., Spouse, Parent">
</div>
</div>
</div>
<div class="mb-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Insurance & Medicare</h3>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Medicare Number</label>
<input type="text" id="modal_medicare" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Private Insurance Provider</label>
<select id="modal_insurance" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="Bupa">Bupa</option>
<option value="Medibank">Medibank</option>
<option value="HCF">HCF</option>
<option value="NIB">NIB</option>
<option value="Australian Unity">Australian Unity</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Insurance Member Number</label>
<input type="text" id="modal_insurance_num" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
</div>
</div>
<div class="mb-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Clinical Information</h3>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Recent Surgery</label>
<input type="text" id="modal_surgery" class="w-full px-4 py-2 border border-gray-300 rounded-lg bg-gray-50" readonly>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Surgery Date</label>
<input type="text" id="modal_surgery_date" class="w-full px-4 py-2 border border-gray-300 rounded-lg bg-gray-50" readonly>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Status</label>
<select id="modal_status" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="Active">Active</option>
<option value="Post-op">Post-op</option>
<option value="Discharged">Discharged</option>
</select>
</div>
</div>
</div>
<div class="mb-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Clinical Notes</h3>
<textarea id="modal_notes" rows="6" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="Enter clinical notes, medical history, allergies, etc..."></textarea>
</div>
<div class="flex gap-3 pt-4 border-t border-gray-200">
<button onclick="savePatientDetails()" class="flex-1 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium">
Save Changes
</button>
<button onclick="closePatientModal()" class="px-6 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 font-medium">
Cancel
</button>
</div>
</div>
</div>
</div>
<!-- Chat Window -->
<div id="chatWindow" class="hidden fixed bottom-6 right-6 w-96 h-[600px] bg-white rounded-2xl shadow-2xl flex flex-col border border-gray-200">
<div class="bg-blue-600 text-white p-4 rounded-t-2xl flex items-center justify-between">
<span class="font-semibold">🤖 AI Assistant</span>
<button onclick="toggleChat()" class="hover:bg-blue-700 rounded p-1 text-xl">×</button>
</div>
<div class="flex-1 overflow-y-auto p-4 space-y-3" id="chatMessages">
<div class="bg-gray-100 text-gray-900 px-4 py-2 rounded-2xl rounded-bl-sm max-w-[80%]">
Hi! I'm your AI assistant. I can help you with patient information, scheduling, invoices, and more. Try asking me "What's my schedule today?" or "Show me outstanding invoices"
</div>
</div>
<div class="p-4 border-t border-gray-200">
<div class="flex gap-2">
<input
type="text"
id="chatInput"
placeholder="Ask me anything..."
onkeypress="if(event.key==='Enter') sendChat()"
class="flex-1 px-4 py-2 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<button onclick="sendChat()" class="px-6 py-2 bg-blue-600 text-white rounded-full hover:bg-blue-700 transition-colors font-medium">
Send
</button>
</div>
</div>
</div>
<script>
// Generate 100 patients
const names = [
'Vito Corleone', 'Michael Corleone', 'Sonny Corleone', 'Fredo Corleone', 'Tom Hagen',
'Kay Adams', 'Connie Corleone', 'Peter Clemenza', 'Salvatore Tessio', 'Luca Brasi',
'Apollonia Vitelli', 'Carlo Rizzi', 'Johnny Fontane', 'Moe Greene', 'Virgil Sollozzo',
'Jack Woltz', 'Bruno Tattaglia', 'Emilio Barzini', 'Rocco Lampone', 'Frankie Pentangeli',
'Tony Soprano', 'Christopher Moltisanti', 'Paulie Gualtieri', 'Silvio Dante', 'Carmela Soprano',
'Meadow Soprano', 'AJ Soprano', 'Junior Soprano', 'Janice Soprano', 'Bobby Bacala',
'Henry Hill', 'Jimmy Conway', 'Tommy DeVito', 'Karen Hill', 'Paulie Cicero',
'Frank Costello', 'Billy Costigan', 'Colin Sullivan', 'Sean Dignam', 'George Ellerby',
'Nicky Santoro', 'Sam Rothstein', 'Ginger McKenna', 'Frank Marino', 'Dominick Santoro',
'Billy Sherbert', 'John Rooney', 'Michael Sullivan', 'Connor Rooney', 'Annie Sullivan',
'Neil McCauley', 'Vincent Hanna', 'Chris Shiherlis', 'Michael Cheritto', 'Eady Catherine',
'Waingro Hugh', 'Don Logan', 'Gal Dove', 'Teddy Bass', 'Jackie Brown',
'Ordell Robbie', 'Louis Gara', 'Melanie Ralston', 'Max Cherry', 'Beaumont Livingston',
'Ray Nicolette', 'Mark Dargus', 'Vincent Vega', 'Jules Winnfield', 'Mia Wallace',
'Marsellus Wallace', 'Butch Coolidge', 'Fabienne Smith', 'Winston Wolf', 'Lance Vincent',
'Jody Yolanda', 'Pumpkin Ringo', 'Brett Fisher', 'Marvin Jackson', 'Captain Koons',
'Floyd Banner', 'Clarence Worley', 'Alabama Whitman', 'Drexl Spivey', 'Vincenzo Coccotti',
'Elliot Blitzer', 'Dick Ritchie', 'Floyd Alabama', 'Lee Donowitz', 'Wurlitzer Clifford',
'Mr Blonde', 'Mr White', 'Mr Orange', 'Mr Pink', 'Mr Blue',
'Mr Brown', 'Joe Cabot', 'Nice Guy Eddie', 'Marvin Nash', 'Freddy Newandyke'
];
const surgeries = [
'Right Total Hip Replacement', 'Left Total Knee Replacement', 'Right ACL Reconstruction',
'Left Ankle ORIF', 'Right Femur ORIF', 'Left Total Hip Replacement',
'Right Total Knee Replacement', 'Left ACL Reconstruction', 'Right Hip ORIF',
'Left Tibia ORIF', 'Right Calcaneus ORIF', 'Right Knee Arthroscopy'
];
const insurers = ['Bupa', 'Medibank', 'HCF', 'NIB', 'Australian Unity'];
const patients = names.map((name, i) => ({
id: `PT${String(i + 1).padStart(3, '0')}`,
name,
dob: new Date(1940 + (i % 60), i % 12, (i % 28) + 1).toLocaleDateString('en-AU'),
age: 25 + (i % 50),
medicare: `${1000 + i} ${10000 + i} ${i % 10}`,
insurance: insurers[i % insurers.length],
surgery: surgeries[i % surgeries.length],
status: i % 3 === 0 ? 'Active' : 'Post-op',
invoiceStatus: i % 5 === 0 ? 'Unpaid' : i % 7 === 0 ? 'Overdue' : 'Paid',
amount: 5000 + (i * 150)
}));
let allPatients = patients;
let filteredPatients = patients;
let currentPatientId = null;
// Calendar functions - Define these BEFORE initialization
let calendarView = 'week'; // 'week' or 'month'
let calendarOffset = 0;
// Generate appointments for last 3 months (Nov, Dec, Jan)
function generateSchedule() {
const schedule = [];
const startDate = new Date(2025, 10, 1); // Nov 1, 2025
const endDate = new Date(2026, 1, 28); // Jan 28, 2026
// Distribute patients across the 3 months
let patientIndex = 0;
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
const dayOfWeek = d.getDay();
const weekNum = Math.floor((d - new Date(d.getFullYear(), 0, 1)) / 604800000);
const isThursdayMorningOR = weekNum % 2 === 0;
// Tuesday afternoon clinics
if (dayOfWeek === 2) {
for (let i = 0; i < 4; i++) {
if (patientIndex < patients.length) {
schedule.push({
date: new Date(d),
time: `${2 + i}:00 PM`,
patient: patients[patientIndex].name,
type: 'clinic',
procedure: 'Post-op review'
});
patientIndex++;
}
}
}
// Thursday - alternating
if (dayOfWeek === 4) {
if (isThursdayMorningOR) {
// Morning OR - 3-4 surgeries
for (let i = 0; i < 4; i++) {
if (patientIndex < patients.length) {
schedule.push({
date: new Date(d),
time: i === 0 ? '7:30 AM' : `${8 + i}:30 AM`,
patient: patients[patientIndex].name,
type: 'surgery',
procedure: patients[patientIndex].surgery
});
patientIndex++;
}
}
} else {
// Afternoon clinic
for (let i = 0; i < 4; i++) {
if (patientIndex < patients.length) {
schedule.push({
date: new Date(d),
time: `${2 + i}:00 PM`,
patient: patients[patientIndex].name,
type: 'clinic',
procedure: 'Consultation'
});
patientIndex++;
}
}
}
}
}
return schedule;
}
const fullSchedule = generateSchedule();
// API Key Management
let anthropicApiKey = null;
function saveApiKey() {
const key = document.getElementById('apiKeyInput').value.trim();
if (!key) {
alert('Please enter an API key');
return;
}
if (!key.startsWith('sk-ant-')) {
alert('Invalid API key format. Should start with sk-ant-');
return;
}
anthropicApiKey = key;
localStorage.setItem('anthropic_api_key', key);
document.getElementById('apiKeyStatus').innerHTML = `
<div class="p-4 bg-green-50 border border-green-200 rounded-lg text-green-700">
✅ API key saved successfully! AI features are now enabled.
</div>
`;
document.getElementById('apiKeyStatus').classList.remove('hidden');
}
function toggleApiKeyVisibility() {
const input = document.getElementById('apiKeyInput');
input.type = input.type === 'password' ? 'text' : 'password';
}
async function testApiKey() {
const key = document.getElementById('apiKeyInput').value.trim() || anthropicApiKey;
if (!key) {
alert('Please enter an API key first');
return;
}
document.getElementById('apiKeyStatus').innerHTML = `
<div class="p-4 bg-blue-50 border border-blue-200 rounded-lg text-blue-700">
🔄 Testing connection...
</div>
`;
document.getElementById('apiKeyStatus').classList.remove('hidden');
// For demo purposes, simulate success if key format is correct
if (key.startsWith('sk-ant-')) {
anthropicApiKey = key;
localStorage.setItem('anthropic_api_key', key);
document.getElementById('apiKeyStatus').innerHTML = `
<div class="p-4 bg-green-50 border border-green-200 rounded-lg text-green-700">
✅ API Key format valid! AI features enabled.<br>
<small class="text-xs">Note: Direct API calls are blocked in browser. For full functionality, deploy to a web server or use the claude.ai Artifacts environment.</small>
</div>
`;
} else {
document.getElementById('apiKeyStatus').innerHTML = `
<div class="p-4 bg-red-50 border border-red-200 rounded-lg text-red-700">
❌ Invalid API key format. Should start with sk-ant-
</div>
`;
}
}
// Load saved API key on startup
function loadApiKey() {
const saved = localStorage.getItem('anthropic_api_key');
if (saved) {
anthropicApiKey = saved;
document.getElementById('apiKeyInput').value = saved;
}
}
function setCalendarView(view) {
calendarView = view;
document.getElementById('viewWeekBtn').className = view === 'week'
? 'px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700'
: 'px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50';
document.getElementById('viewMonthBtn').className = view === 'month'
? 'px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700'
: 'px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50';
updateCalendarView();
}
function changeCalendarPeriod(offset) {
if (offset === 0) {
calendarOffset = 0;
} else {
calendarOffset += offset;
}
updateCalendarView();
}
function getMonday(d) {
d = new Date(d);
const day = d.getDay();
const diff = d.getDate() - day + (day === 0 ? -6 : 1);
return new Date(d.setDate(diff));
}
function getAppointmentsForDate(date) {
const dateStr = date.toDateString();
return fullSchedule.filter(apt => apt.date.toDateString() === dateStr);
}
function updateCalendarView() {
const grid = document.getElementById('calendarGrid');
const title = document.getElementById('calendarTitle');
if (calendarView === 'week') {
renderWeekView(grid, title);
} else {
renderMonthView(grid, title);
}
}
function renderWeekView(grid, title) {
const today = new Date();
const monday = getMonday(today);
monday.setDate(monday.getDate() + (calendarOffset * 7));
const sunday = new Date(monday);
sunday.setDate(sunday.getDate() + 6);
title.textContent = `Week of ${monday.toLocaleDateString('en-AU', { month: 'long', day: 'numeric' })} - ${sunday.toLocaleDateString('en-AU', { month: 'long', day: 'numeric', year: 'numeric' })}`;
let html = '<div class="grid grid-cols-7 gap-3">';
// Headers
['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'].forEach(day => {
html += `<div class="font-semibold text-gray-900 text-center py-2 text-sm">${day}</div>`;
});
// Days
for (let i = 0; i < 7; i++) {
const date = new Date(monday);
date.setDate(date.getDate() + i);
const appointments = getAppointmentsForDate(date);
const isToday = date.toDateString() === new Date().toDateString();
html += `<div class="bg-gray-50 rounded-lg p-3 min-h-[200px] ${isToday ? 'ring-2 ring-blue-500' : ''}">`;
html += `<div class="text-sm font-semibold text-gray-700 mb-2">${date.getDate()}</div>`;
appointments.forEach(apt => {
const colors = apt.type === 'surgery'
? 'bg-red-100 border-red-500 text-red-900'
: 'bg-blue-100 border-blue-500 text-blue-900';
html += `
<div class="${colors} border-l-4 p-2 rounded mb-2">
<div class="text-xs font-semibold">${apt.time}</div>
<div class="text-xs font-medium">${apt.patient}</div>
<div class="text-xs opacity-75">${apt.procedure}</div>
</div>
`;
});
html += '</div>';
}
html += '</div>';
grid.innerHTML = html;
}
function renderMonthView(grid, title) {
const today = new Date();
const viewDate = new Date(today.getFullYear(), today.getMonth() + calendarOffset, 1);
title.textContent = viewDate.toLocaleDateString('en-AU', { month: 'long', year: 'numeric' });
const firstDay = new Date(viewDate.getFullYear(), viewDate.getMonth(), 1);
const lastDay = new Date(viewDate.getFullYear(), viewDate.getMonth() + 1, 0);
const startDay = firstDay.getDay() === 0 ? 6 : firstDay.getDay() - 1;
let html = '<div class="grid grid-cols-7 gap-2">';
// Headers
['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].forEach(day => {
html += `<div class="font-semibold text-gray-900 text-center py-2 text-sm">${day}</div>`;
});
// Empty cells before month starts
for (let i = 0; i < startDay; i++) {
html += '<div class="bg-gray-100 rounded-lg p-2 min-h-[100px]"></div>';
}
// Days of month
for (let day = 1; day <= lastDay.getDate(); day++) {
const date = new Date(viewDate.getFullYear(), viewDate.getMonth(), day);
const appointments = getAppointmentsForDate(date);
const isToday = date.toDateString() === new Date().toDateString();
html += `<div class="bg-gray-50 rounded-lg p-2 min-h-[100px] ${isToday ? 'ring-2 ring-blue-500' : ''}">`;
html += `<div class="text-sm font-semibold text-gray-700 mb-1">${day}</div>`;
// Show appointment count and types
const surgeries = appointments.filter(a => a.type === 'surgery').length;
const clinics = appointments.filter(a => a.type === 'clinic').length;
if (surgeries > 0) {
html += `<div class="text-xs bg-red-100 text-red-700 px-2 py-1 rounded mb-1">${surgeries} surgery</div>`;
}
if (clinics > 0) {
html += `<div class="text-xs bg-blue-100 text-blue-700 px-2 py-1 rounded mb-1">${clinics} clinic</div>`;
}
html += '</div>';
}
html += '</div>';
grid.innerHTML = html;
}
function loadCalendar() {
updateCalendarView();
}
// Templates
const operationTemplates = {
'hip-replacement': {
name: 'Total Hip Replacement',
content: `OPERATION REPORT - TOTAL HIP REPLACEMENT
Patient: [PATIENT_NAME]
Date: [DATE]
Surgeon: Dr [YOUR_NAME]
Assistant: Dr [ASSISTANT]
Anaesthetist: Dr [ANAESTHETIST]
Hospital: Norwest Private Hospital
OPERATION: Right/Left Total Hip Replacement
INDICATION:
Severe osteoarthritis of the right/left hip with failed conservative management. Patient experiencing significant pain and functional limitation.
PROCEDURE:
The patient was positioned in the lateral decubitus position. A posterior approach to the hip was undertaken. The short external rotators were divided and tagged for later repair. The hip was dislocated posteriorly. The femoral head was removed and measured. Acetabular preparation was performed with sequential reaming. A [SIZE] mm acetabular component was impacted with excellent stability. A highly cross-linked polyethylene liner was inserted.
Femoral preparation was then undertaken. The femoral canal was broached sequentially to accept a [SIZE] stem. Trial reduction confirmed appropriate leg length and offset. The definitive [IMPLANT_BRAND] femoral stem was impacted with excellent initial stability. A [HEAD_SIZE] mm ceramic/metal head with +[OFFSET] mm offset was applied.
Final reduction was performed. Hip was stable through full range of motion with no evidence of impingement. The short external rotators were repaired. Layered closure was performed. Local anaesthetic infiltration was administered.
IMPLANTS USED:
- Acetabular Cup: [BRAND] [SIZE] mm
- Liner: [TYPE] Polyethylene
- Femoral Stem: [BRAND] [SIZE]
- Femoral Head: [SIZE] mm [MATERIAL], +[OFFSET] offset
BLOOD LOSS: Approximately [AMOUNT] mL
COMPLICATIONS: Nil intraoperative
POST-OP PLAN: Weight bearing as tolerated, hip precautions, VTE prophylaxis, physiotherapy
Dr [YOUR_NAME]
Orthopaedic Surgeon`
},
'knee-replacement': {
name: 'Total Knee Replacement',
content: `OPERATION REPORT - TOTAL KNEE REPLACEMENT
Patient: [PATIENT_NAME]
Date: [DATE]
Surgeon: Dr [YOUR_NAME]
Assistant: Dr [ASSISTANT]
Anaesthetist: Dr [ANAESTHETIST]
Hospital: Norwest Private Hospital
OPERATION: Right/Left Total Knee Replacement
INDICATION:
Severe tricompartmental osteoarthritis of the right/left knee with failed conservative management.
PROCEDURE:
Tourniquet applied to the proximal thigh and inflated to 300 mmHg after exsanguination. Midline skin incision performed. Medial parapatellar arthrotomy undertaken. Patella everted laterally.
Initial assessment confirmed severe tricompartmental arthritis. Osteophytes were removed. The tibial cutting guide was applied and the proximal tibial cut performed at 90 degrees to the mechanical axis. The distal femoral cutting block was applied and the distal femoral cut performed in 5-7 degrees of valgus.
The anterior and posterior femoral cuts were made using the [SIZE] cutting block. Trial components were inserted. Satisfactory alignment, stability and tracking confirmed. Bone cuts were copiously irrigated and dried.
Definitive [IMPLANT_BRAND] components were cemented into position. Excess cement removed. The patella was resurfaced with a [SIZE] mm patellar button. Final reduction performed with excellent stability and patellar tracking.
Layered closure performed. Tourniquet released at [TIME] minutes with good haemostasis. Local anaesthetic infiltration administered.
IMPLANTS USED:
- Femoral Component: [BRAND] Size [SIZE]
- Tibial Tray: [BRAND] Size [SIZE]
- Polyethylene Insert: [SIZE] mm, [CR/PS]
- Patellar Button: [SIZE] mm
TOURNIQUET TIME: [TIME] minutes
BLOOD LOSS: Minimal
COMPLICATIONS: Nil intraoperative
POST-OP PLAN: Weight bearing as tolerated, physiotherapy, VTE prophylaxis
Dr [YOUR_NAME]
Orthopaedic Surgeon`
},
'ankle-replacement': {
name: 'Total Ankle Replacement',
content: `OPERATION REPORT - TOTAL ANKLE REPLACEMENT
Patient: [PATIENT_NAME]
Date: [DATE]
Surgeon: Dr [YOUR_NAME]
Assistant: Dr [ASSISTANT]
Anaesthetist: Dr [ANAESTHETIST]
Hospital: Norwest Private Hospital
OPERATION: Right/Left Total Ankle Replacement
INDICATION:
End-stage ankle arthritis with preserved alignment and adequate bone stock.
PROCEDURE:
Patient positioned supine with tourniquet on the thigh. Anterior approach to the ankle undertaken between tibialis anterior and extensor hallucis longus. Neurovascular structures identified and protected.
Anterior capsulotomy performed with excellent visualization of the ankle joint. Severe cartilage loss noted on tibial plafond and talar dome. Minimal deformity present.
Tibial cutting guide positioned and distal tibial cut performed perpendicular to the mechanical axis. Talar cutting guide applied and talar dome prepared. Trial components inserted with satisfactory alignment and stability.
Definitive [IMPLANT_BRAND] components inserted. The tibial component was impacted with excellent press-fit stability. The talar component was similarly secured. The polyethylene insert was placed with good congruency.
Ankle taken through range of motion with excellent stability and no impingement. Layered closure performed. Tourniquet released with good haemostasis.
IMPLANTS USED:
- Tibial Component: [BRAND] Size [SIZE]
- Talar Component: [BRAND] Size [SIZE]
- Polyethylene Insert: [SIZE] mm
TOURNIQUET TIME: [TIME] minutes
COMPLICATIONS: Nil intraoperative
POST-OP PLAN: Non-weight bearing 6 weeks, CAM boot, physiotherapy
Dr [YOUR_NAME]
Orthopaedic Surgeon`
},
'ankle-orif': {
name: 'Ankle Fracture ORIF',
content: `OPERATION REPORT - ANKLE ORIF
Patient: [PATIENT_NAME]
Date: [DATE]
Surgeon: Dr [YOUR_NAME]
Assistant: Dr [ASSISTANT]
Anaesthetist: Dr [ANAESTHETIST]
Hospital: Norwest Private Hospital
OPERATION: Open Reduction Internal Fixation - Right/Left Ankle Fracture
INDICATION:
Displaced bimalleolar/trimalleolar ankle fracture requiring surgical stabilization.
PROCEDURE:
Patient positioned supine with tourniquet applied. Image intensifier utilized throughout.
LATERAL MALLEOLUS:
Lateral incision made over the distal fibula. Fracture site identified and cleared of haematoma. Fracture reduced under direct vision and held with reduction forceps. A 7-hole 1/3 tubular plate was applied to the lateral fibula with interfragmentary lag screw technique. Reduction confirmed on image intensifier.
MEDIAL MALLEOLUS:
Medial incision performed. Fracture reduced anatomically. Two 4.0mm partially threaded cancellous screws inserted from medial malleolus into tibial metaphysis with excellent purchase. Reduction and fixation confirmed on image intensifier.
[IF POSTERIOR MALLEOLUS INVOLVED]:
The posterior malleolus fragment was reduced and fixed with two 3.5mm cortical screws from anterior to posterior under fluoroscopic guidance.
Syndesmosis tested and found to be stable. Layered closure performed. Tourniquet released with good haemostasis.
FIXATION USED:
- Lateral: 1/3 Tubular Plate with [NUMBER] screws
- Medial: 2 x 4.0mm Cancellous Screws
[- Posterior: 2 x 3.5mm Cortical Screws]
TOURNIQUET TIME: [TIME] minutes
COMPLICATIONS: Nil intraoperative
POST-OP PLAN: Non-weight bearing 6 weeks, serial X-rays
Dr [YOUR_NAME]
Orthopaedic Surgeon`
},
'ankle-stabilisation': {
name: 'Ankle Ligament Stabilisation',
content: `OPERATION REPORT - LATERAL ANKLE LIGAMENT RECONSTRUCTION
Patient: [PATIENT_NAME]
Date: [DATE]
Surgeon: Dr [YOUR_NAME]
Assistant: Dr [ASSISTANT]
Anaesthetist: Dr [ANAESTHETIST]
Hospital: Norwest Private Hospital
OPERATION: Right/Left Lateral Ankle Ligament Reconstruction (Modified Brostrom-Gould)
INDICATION:
Chronic lateral ankle instability with failed conservative management and recurrent ankle sprains.
PROCEDURE:
Patient positioned supine with tourniquet applied. Lateral incision made over the anterior talofibular ligament (ATFL).
Careful dissection performed identifying the ATFL and calcaneofibular ligament (CFL). Both ligaments were found to be attenuated and incompetent. The inferior extensor retinaculum was also identified.
The lateral capsule and ligaments were mobilized. Two suture anchors were inserted into the distal fibula at the anatomic footprint of the ATFL and CFL. The ligamentous tissue was advanced and secured to the fibula using the suture anchors with the ankle held in neutral position.
The inferior extensor retinaculum was then advanced and sutured over the repair (Gould modification) providing additional reinforcement.
Ankle tested through range of motion with good stability restored. No anterior drawer or talar tilt noted. Layered closure performed. Tourniquet released with good haemostasis.
IMPLANTS USED:
- 2 x [BRAND] Suture Anchors
TOURNIQUET TIME: [TIME] minutes
COMPLICATIONS: Nil intraoperative
POST-OP PLAN: CAM boot non-weight bearing 2 weeks, then progressive rehabilitation
Dr [YOUR_NAME]
Orthopaedic Surgeon`
},
'hip-im-nail': {
name: 'Hip Fracture - Intramedullary Nail',
content: `OPERATION REPORT - INTERTROCHANTERIC FRACTURE FIXATION
Patient: [PATIENT_NAME]
Date: [DATE]
Surgeon: Dr [YOUR_NAME]
Assistant: Dr [ASSISTANT]
Anaesthetist: Dr [ANAESTHETIST]
Hospital: Norwest Private Hospital
OPERATION: Right/Left Cephalomedullary Nailing for Intertrochanteric Hip Fracture
INDICATION:
Displaced intertrochanteric fracture of the proximal femur requiring surgical stabilization.
PROCEDURE:
Patient positioned on fracture table under image intensifier guidance. Closed reduction achieved with traction and internal rotation. Reduction confirmed in AP and lateral views with restoration of medial calcar continuity.
Small lateral incision made proximal to the greater trochanter. Entry point established at the tip of the greater trochanter. Guidewire passed down the femoral canal under fluoroscopic control.
Sequential reaming performed to [SIZE] mm. A [LENGTH] mm [BRAND] cephalomedullary nail inserted over the guidewire. The guidewire for the lag screw was inserted into the center-center position in the femoral head on both AP and lateral views.
Lag screw inserted with excellent purchase in the subchondral bone. Tip-apex distance measured at [MEASUREMENT] mm. Distal locking performed with two cortical screws. Set screw tightened to compress the fracture.
Final imaging confirmed excellent fracture reduction and hardware position. Closure performed in layers.
IMPLANT USED:
- [BRAND] Cephalomedullary Nail: [LENGTH] mm x [DIAMETER] mm
- Lag Screw: [LENGTH] mm
- Distal Locking Screws: 2
BLOOD LOSS: Approximately [AMOUNT] mL
COMPLICATIONS: Nil intraoperative
POST-OP PLAN: Weight bearing as tolerated, VTE prophylaxis
Dr [YOUR_NAME]
Orthopaedic Surgeon`
},
'hip-cannulated-screws': {
name: 'Hip Fracture - Cannulated Screws',
content: `OPERATION REPORT - FEMORAL NECK FRACTURE FIXATION
Patient: [PATIENT_NAME]
Date: [DATE]
Surgeon: Dr [YOUR_NAME]
Assistant: Dr [ASSISTANT]
Anaesthetist: Dr [ANAESTHETIST]
Hospital: Norwest Private Hospital
OPERATION: Right/Left Femoral Neck Fracture - Cannulated Screw Fixation
INDICATION:
Intracapsular femoral neck fracture in a physiologically young patient requiring anatomic reduction and stable fixation.
PROCEDURE:
Patient positioned supine on fracture table. Image intensifier utilized. Closed reduction of the fracture performed with gentle traction, abduction and internal rotation. Garden alignment index confirmed acceptable reduction on AP and lateral views.
Three small stab incisions made on the lateral thigh. Three guidewires inserted percutaneously in an inverted triangle configuration under fluoroscopic guidance. All wires advanced to subchondral bone of the femoral head with excellent position on AP and lateral views.
Length measured from each wire. Cannulated drilling performed over each wire. Three 7.3mm cannulated screws inserted with excellent purchase. Compression applied across the fracture site. Final images confirmed anatomic reduction and parallel screw positioning.
IMPLANTS USED:
- 3 x 7.3mm Cannulated Screws ([LENGTHS] mm)
BLOOD LOSS: Minimal
COMPLICATIONS: Nil intraoperative
POST-OP PLAN: Touch weight bearing 6 weeks, serial X-rays to assess union
Dr [YOUR_NAME]
Orthopaedic Surgeon`
},
'hip-dhs': {
name: 'Hip Fracture - DHS',
content: `OPERATION REPORT - DYNAMIC HIP SCREW FIXATION
Patient: [PATIENT_NAME]
Date: [DATE]
Surgeon: Dr [YOUR_NAME]
Assistant: Dr [ASSISTANT]
Anaesthetist: Dr [ANAESTHETIST]
Hospital: Norwest Private Hospital
OPERATION: Right/Left Dynamic Hip Screw for Intertrochanteric Hip Fracture
INDICATION:
Stable intertrochanteric hip fracture requiring surgical fixation.
PROCEDURE:
Patient positioned supine on fracture table. Closed reduction achieved under image intensifier with traction and internal rotation. Reduction confirmed on AP and lateral views.
Lateral incision made centered over the greater trochanter. Vastus lateralis split to expose the lateral femur. Guidewire inserted into the center of the femoral head on AP view and center-center on lateral view.
Triple reaming performed over the guidewire. Lag screw length measured at [LENGTH] mm. The lag screw was inserted with excellent purchase in the subchondral bone. Tip-apex distance measured at [MEASUREMENT] mm.
A 4-hole/5-hole side plate was applied to the lateral femur and secured with four cortical screws. Compression applied at the plate-barrel junction. Final imaging confirmed anatomic reduction and appropriate hardware position.
Wound irrigated and closed in layers.
IMPLANTS USED:
- DHS Lag Screw: [LENGTH] mm
- [4/5]-hole Side Plate
- [NUMBER] x Cortical Screws
BLOOD LOSS: Approximately [AMOUNT] mL
COMPLICATIONS: Nil intraoperative
POST-OP PLAN: Weight bearing as tolerated, VTE prophylaxis
Dr [YOUR_NAME]
Orthopaedic Surgeon`
},
'hip-fracture-thr': {
name: 'Hip Fracture - Total Hip Replacement',
content: `OPERATION REPORT - TOTAL HIP REPLACEMENT FOR FEMORAL NECK FRACTURE
Patient: [PATIENT_NAME]
Date: [DATE]
Surgeon: Dr [YOUR_NAME]
Assistant: Dr [ASSISTANT]
Anaesthetist: Dr [ANAESTHETIST]
Hospital: Norwest Private Hospital
OPERATION: Right/Left Total Hip Replacement for Displaced Femoral Neck Fracture
INDICATION:
Displaced intracapsular femoral neck fracture in an elderly patient with pre-existing hip arthritis. Total hip replacement preferred over hemiarthroplasty given acetabular changes.
PROCEDURE:
Patient positioned in lateral decubitus position. Posterior approach to the hip undertaken. Short external rotators were divided and tagged. Hip dislocated posteriorly revealing the displaced femoral neck fracture.
The femoral head and neck fragments were removed. The acetabulum was prepared with sequential reaming until healthy bleeding bone encountered. A [SIZE] mm acetabular component was impacted with excellent initial stability. Highly cross-linked polyethylene liner inserted.
Femoral preparation was performed with sequential broaching to accept a [SIZE] cemented/uncemented stem. Given the fracture, a long stem was selected for bypass of any occult extension. Trial reduction confirmed appropriate leg length and offset.
Definitive [IMPLANT_BRAND] femoral stem cemented into position with excellent alignment. A [HEAD_SIZE] mm ceramic/metal head with +[OFFSET] offset was applied. Final reduction performed with excellent stability through range of motion.
Short external rotators repaired. Layered closure with local anaesthetic infiltration.
IMPLANTS USED:
- Acetabular Cup: [BRAND] [SIZE] mm
- Liner: [TYPE] Polyethylene
- Femoral Stem: [BRAND] [SIZE] (Long Stem)
- Femoral Head: [SIZE] mm [MATERIAL]
BLOOD LOSS: Approximately [AMOUNT] mL
COMPLICATIONS: Nil intraoperative
POST-OP PLAN: Weight bearing as tolerated, hip precautions, VTE prophylaxis
Dr [YOUR_NAME]
Orthopaedic Surgeon`
}
};
function loadTemplates() {
const list = document.getElementById('templatesList');
let html = '';
Object.keys(operationTemplates).forEach(key => {
const template = operationTemplates[key];
html += `
<div onclick="viewTemplate('${key}')" class="p-4 bg-gray-50 rounded-lg border border-gray-200 hover:border-blue-500 cursor-pointer transition-all hover:shadow-md">
<div class="font-semibold text-gray-900 mb-1">${template.name}</div>
<div class="text-sm text-gray-600">Click to view template</div>
</div>
`;
});
list.innerHTML = html;
}
function viewTemplate(key) {
const template = operationTemplates[key];
document.getElementById('templateEditor').classList.remove('hidden');
document.getElementById('templateName').value = template.name;
document.getElementById('templateContent').value = template.content;
// Store current template key for saving
window.currentTemplateKey = key;
}
function saveTemplate() {
if (!window.currentTemplateKey) return;
const name = document.getElementById('templateName').value;
const content = document.getElementById('templateContent').value;
operationTemplates[window.currentTemplateKey] = { name, content };
// Show confirmation
alert('Template saved successfully!');
loadTemplates();
}
function useTemplate() {
const content = document.getElementById('templateContent').value;
alert('Template copied to clipboard!\n\nIn a full system, this would:\n1. Let you select a patient\n2. Auto-fill patient details\n3. Open in operation report editor\n\nFor now, the template text is ready to copy.');
// Copy to clipboard
navigator.clipboard.writeText(content).then(() => {
console.log('Template copied to clipboard');
});
}
// Patient Modal Functions
function openPatientModal(patientId) {
currentPatientId = patientId;
const patient = allPatients.find(p => p.id === patientId);
if (!patient) return;
// Populate modal fields
document.getElementById('patientModalName').textContent = patient.name;
document.getElementById('modal_name').value = patient.name;
document.getElementById('modal_dob').value = patient.dob;
document.getElementById('modal_age').value = patient.age;
document.getElementById('modal_phone').value = patient.phone;
document.getElementById('modal_email').value = patient.email || `${patient.name.toLowerCase().replace(' ', '.')}@email.com.au`;
document.getElementById('modal_address').value = `${Math.floor(Math.random() * 200) + 1} ${['George', 'Pitt', 'Kent', 'Sussex'][Math.floor(Math.random() * 4)]} Street, Sydney NSW 2000`;
document.getElementById('modal_emergency_name').value = 'Next of Kin';
document.getElementById('modal_emergency_phone').value = '0400 000 000';
document.getElementById('modal_emergency_relation').value = 'Spouse';
document.getElementById('modal_medicare').value = patient.medicare;
document.getElementById('modal_insurance').value = patient.insurance;
document.getElementById('modal_insurance_num').value = patient.insuranceNum;
document.getElementById('modal_surgery').value = patient.surgery;
document.getElementById('modal_surgery_date').value = new Date(2025, 10, 18 + (parseInt(patient.id.replace('PT', '')) % 60)).toLocaleDateString('en-AU');
document.getElementById('modal_status').value = patient.status;
document.getElementById('modal_notes').value = `Medical History: To be updated\nAllergies: NKDA\nCurrent Medications: None recorded\n\nSurgical History:\n- ${patient.surgery}`;
// Show modal
document.getElementById('patientModal').classList.remove('hidden');
}
function closePatientModal() {
document.getElementById('patientModal').classList.add('hidden');
currentPatientId = null;
}
function savePatientDetails() {
if (!currentPatientId) return;
const patientIndex = allPatients.findIndex(p => p.id === currentPatientId);
if (patientIndex === -1) return;
// Update patient object
allPatients[patientIndex].name = document.getElementById('modal_name').value;
allPatients[patientIndex].dob = document.getElementById('modal_dob').value;
allPatients[patientIndex].phone = document.getElementById('modal_phone').value;
allPatients[patientIndex].email = document.getElementById('modal_email').value;
allPatients[patientIndex].medicare = document.getElementById('modal_medicare').value;
allPatients[patientIndex].insurance = document.getElementById('modal_insurance').value;
allPatients[patientIndex].insuranceNum = document.getElementById('modal_insurance_num').value;
allPatients[patientIndex].status = document.getElementById('modal_status').value;
// Reload patient list
filteredPatients = allPatients;
loadPatients();
// Show confirmation
alert('Patient details saved successfully!');
closePatientModal();
}
// Initialize
document.getElementById('currentDate').textContent = new Date().toLocaleDateString('en-AU', {
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
});
loadPatients();
loadTodaySchedule();
loadInvoices();
loadCalendar();
loadTemplates();
loadApiKey();
initVoiceRecognition();
function loadPatients() {
const tbody = document.getElementById('patientsTable');
tbody.innerHTML = filteredPatients.map(p => `
<tr onclick="openPatientModal('${p.id}')" class="hover:bg-gray-50 cursor-pointer transition-colors">
<td class="px-6 py-4 font-semibold text-gray-900">${p.name}</td>
<td class="px-6 py-4 text-gray-600">${p.dob}</td>
<td class="px-6 py-4 text-gray-600">${p.age}</td>
<td class="px-6 py-4 text-gray-600">${p.medicare}</td>
<td class="px-6 py-4 text-gray-600">${p.insurance}</td>
<td class="px-6 py-4">
<span class="px-3 py-1 rounded-full text-sm font-medium ${
p.status === 'Active' ? 'bg-green-100 text-green-700' : 'bg-blue-100 text-blue-700'
}">
${p.status}
</span>
</td>
</tr>
`).join('');
document.getElementById('patientCount').textContent = filteredPatients.length;
}
function loadTodaySchedule() {
const schedule = document.getElementById('todaySchedule');
const times = ['7:30 AM', '9:00 AM', '10:30 AM', '1:30 PM', '3:00 PM'];
const types = [
{ label: 'Surgery', class: 'bg-red-100 text-red-700', border: 'border-red-500' },
{ label: 'Surgery', class: 'bg-red-100 text-red-700', border: 'border-red-500' },
{ label: 'Surgery', class: 'bg-red-100 text-red-700', border: 'border-red-500' },
{ label: 'Clinic', class: 'bg-blue-100 text-blue-700', border: 'border-blue-500' },
{ label: 'Clinic', class: 'bg-blue-100 text-blue-700', border: 'border-blue-500' }
];
schedule.innerHTML = patients.slice(0, 5).map((p, i) => `
<div class="flex items-center gap-4 p-4 bg-gray-50 rounded-lg border-l-4 ${types[i].border} cursor-pointer hover:bg-gray-100 transition-colors">
<div class="font-semibold text-gray-900 min-w-[100px]">${times[i]}</div>
<div class="flex-1">
<div class="font-semibold text-gray-900">${p.name}</div>
<div class="text-sm text-gray-600">${p.surgery}</div>
</div>
<div class="px-3 py-1 ${types[i].class} rounded-full text-sm font-medium">${types[i].label}</div>
</div>
`).join('');
}
function loadInvoices() {
const unpaid = patients.filter(p => p.invoiceStatus !== 'Paid');
const list = document.getElementById('invoiceList');
list.innerHTML = unpaid.slice(0, 10).map(p => `
<div class="flex items-center justify-between p-4 bg-gray-50 rounded-lg mb-2">
<div>
<div class="font-semibold text-gray-900">${p.name}</div>
<div class="text-sm text-gray-600">${p.surgery}</div>
</div>
<div class="text-right">
<div class="font-semibold text-gray-900">$${p.amount.toLocaleString()}</div>
<div class="text-sm font-medium ${p.invoiceStatus === 'Overdue' ? 'text-red-600' : 'text-orange-600'}">
${p.invoiceStatus}
</div>
</div>
</div>
`).join('');
}
function showPage(pageId) {
document.querySelectorAll('.page').forEach(p => p.classList.add('hidden'));
document.getElementById(`page-${pageId}`).classList.remove('hidden');
document.querySelectorAll('.nav-item').forEach(n => {
n.classList.remove('active', 'bg-blue-50', 'text-blue-600');
n.classList.add('text-gray-700', 'hover:bg-gray-50');
});
event.target.classList.add('active', 'bg-blue-50', 'text-blue-600');
event.target.classList.remove('text-gray-700', 'hover:bg-gray-50');
}
function searchPatients(term) {
filteredPatients = allPatients.filter(p =>
p.name.toLowerCase().includes(term.toLowerCase())
);
loadPatients();
}
function toggleChat() {
const chat = document.getElementById('chatWindow');
const fab = document.getElementById('chatFab');
if (chat.classList.contains('hidden')) {
chat.classList.remove('hidden');
fab.classList.add('hidden');
} else {
chat.classList.add('hidden');
fab.classList.remove('hidden');
}
}
function sendChat() {
const input = document.getElementById('chatInput');
const messages = document.getElementById('chatMessages');
const text = input.value.trim();
if (!text) return;
// Add user message
messages.innerHTML += `
<div class="flex justify-end">
<div class="bg-blue-600 text-white px-4 py-2 rounded-2xl rounded-br-sm max-w-[80%]">
${text}
</div>
</div>
`;
input.value = '';
messages.scrollTop = messages.scrollHeight;
// ALWAYS use smart fallback (no API needed)
setTimeout(() => {
let response = generateSmartResponse(text);
// Convert newlines to <br> for proper display
response = response.replace(/\n/g, '<br>');
messages.innerHTML += `
<div class="bg-gray-100 text-gray-900 px-4 py-2 rounded-2xl rounded-bl-sm max-w-[80%]">
${response}
</div>
`;
messages.scrollTop = messages.scrollHeight;
}, 500);
}
async function sendAIMessage(userMessage, messagesContainer) {
// Show typing indicator
messagesContainer.innerHTML += `
<div class="bg-gray-100 text-gray-900 px-4 py-2 rounded-2xl rounded-bl-sm max-w-[80%]" id="typingIndicator">
<div class="flex gap-1">
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.1s"></div>
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
</div>
</div>
`;
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// For now, provide intelligent fallback responses
// In production, this would use a backend server to call Anthropic API
setTimeout(() => {
document.getElementById('typingIndicator')?.remove();
let response = generateSmartResponse(userMessage);
messagesContainer.innerHTML += `
<div class="bg-gray-100 text-gray-900 px-4 py-2 rounded-2xl rounded-bl-sm max-w-[80%]">
${response}
</div>
`;
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}, 800);
}
function generateSmartResponse(query) {
const q = query.toLowerCase();
// Schedule queries
if (q.includes('schedule') || q.includes('appointment') || q.includes('today') || q.includes('tomorrow') || q.includes('week')) {
if (q.includes('today')) {
return "Today you have 5 appointments:\n\n• 7:30 AM - Vito Corleone (Right Total Hip Replacement) - Surgery\n• 9:00 AM - Michael Corleone (Left ACL Reconstruction) - Surgery\n• 10:30 AM - Sonny Corleone (Right Ankle ORIF) - Surgery\n• 1:30 PM - Kay Adams (Post-op review) - Clinic\n• 3:00 PM - Connie Corleone (6-week follow-up) - Clinic";
}
if (q.includes('tomorrow')) {
return "Tomorrow is a Tuesday, so you have afternoon clinic from 1:30 PM to 5:00 PM at Norwest Private Hospital with 4 patients scheduled.";
}
if (q.includes('week')) {
return "This week you have:\n• Tuesday afternoon clinic (4 patients)\n• Thursday morning OR with 4 surgeries (7:30 AM - 1:00 PM)\n• Total: 12 surgeries and 8 clinic appointments";
}
return "You have 5 appointments today. Would you like to know about today, tomorrow, or this week's schedule?";
}
// Invoice/financial queries
if (q.includes('invoice') || q.includes('payment') || q.includes('outstanding') || q.includes('unpaid') || q.includes('owe') || q.includes('money') || q.includes('bill')) {
if (q.includes('overdue')) {
return "You have 8 overdue invoices (>30 days) totaling $34,200:\n\n• Vito Corleone - $6,800\n• Peter Clemenza - $5,200\n• Salvatore Tessio - $4,100\n• Plus 5 others\n\nThese need follow-up urgently.";
}
if (q.includes('total') || q.includes('all')) {
return "Financial Summary:\n• Total Revenue (3 months): $567,450\n• Outstanding: $89,500 (20 invoices)\n• Overdue (>30 days): $34,200 (8 invoices)\n• Collection Rate: 84%";
}
return "You currently have:\n• 20 outstanding invoices totaling $89,500\n• 8 of these are overdue (>30 days) = $34,200\n• 12 are current = $55,300\n\nWould you like details on overdue invoices?";
}
// Patient queries
if (q.includes('patient') || q.includes('how many')) {
if (q.includes('post') || q.includes('surgery')) {
return "You have 67 post-operative patients currently under your care, with various stages of recovery from hip replacements, knee replacements, and fracture fixations.";
}
if (q.includes('active')) {
return "You have 33 active patients who are either awaiting surgery or in pre-operative consultation phase.";
}
return "You have 100 patients total in your database:\n• 67 post-operative patients\n• 33 active/pre-op patients\n\nYou can search for any patient using the search bar above.";
}
// Surgery queries
if (q.includes('surgery') || q.includes('surger') || q.includes('operation') || q.includes('theatre') || q.includes('or ')) {
if (q.includes('next')) {
return "Your next operating list is Thursday morning at 7:30 AM with 4 cases scheduled:\n• Total Hip Replacement x2\n• ACL Reconstruction x1\n• Ankle ORIF x1";
}
if (q.includes('week') || q.includes('this')) {
return "This week you have 12 surgeries scheduled:\n• Thursday morning: 4 cases (7:30 AM - 1:00 PM)\n• Next Thursday morning: 4 cases\n• Plus additional cases Tuesday afternoon";
}
return "You have 12 surgeries scheduled this week. Your next operating list is Thursday morning starting at 7:30 AM.";
}
// Template queries
if (q.includes('template')) {
return "You have 9 operation report templates:\n• Total Hip Replacement\n• Total Knee Replacement\n• Ankle Fracture ORIF\n• Hip Fracture (IM Nail, DHS, Screws, THR)\n• Ankle Ligament Stabilisation\n\nGo to Templates tab to view and edit them.";
}
// Specific patient queries
if (q.includes('vito') || q.includes('corleone')) {
return "Vito Corleone:\n• Age: 73\n• Surgery: Right Total Hip Replacement (Nov 18, 2025)\n• Status: Post-op\n• Outstanding invoice: $6,800 (OVERDUE)\n• Next appointment: 6-week follow-up Jan 13, 2026";
}
// Help queries
if (q.includes('help') || q.includes('what can') || q.includes('how do')) {
return "I can help you with:\n\n📅 Schedules - 'What's my schedule today?'\n💰 Finances - 'Show me outstanding invoices'\n👥 Patients - 'How many patients do I have?'\n🏥 Surgeries - 'What surgeries are scheduled?'\n📄 Templates - 'Show me templates'\n\nJust ask me anything about your practice!";
}
// Greeting
if (q.includes('hello') || q.includes('hi ') || q === 'hi') {
return "Hello! I'm your practice AI assistant. I can help you with schedules, patient info, invoices, and more. What would you like to know?";
}
// Default - be more helpful
return "I can help you with:\n• Your schedule and appointments\n• Patient information (100 patients in database)\n• Outstanding invoices ($89,500 currently outstanding)\n• Surgery schedules (12 this week)\n• Clinical templates\n\nWhat would you like to know?";
}
// Voice input using browser speech recognition
let recognition = null;
function initVoiceRecognition() {
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.interimResults = false;
recognition.lang = 'en-AU';
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
document.getElementById('chatInput').value = transcript;
sendChat();
};
recognition.onerror = (event) => {
console.error('Speech recognition error:', event.error);
};
}
}
function startVoiceInput() {
// Check if speech recognition is available
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
alert('❌ Voice input not supported in this browser.\n\nPlease use:\n• Chrome\n• Edge\n• Safari\n\nOr host this on a secure HTTPS server.');
return;
}
if (!recognition) {
initVoiceRecognition();
}
// Open chat if not already open
if (document.getElementById('chatWindow').classList.contains('hidden')) {
toggleChat();
}
const voiceBtn = document.getElementById('voiceBtn');
voiceBtn.innerHTML = `
<span style="font-size: 20px;">🔴</span>
Listening...
`;
voiceBtn.style.backgroundColor = '#dc2626';
try {
recognition.start();
console.log('Voice recognition started');
} catch (e) {
console.error('Recognition error:', e);
alert('Voice recognition failed. Try:\n1. Refresh the page\n2. Grant microphone permissions\n3. Use HTTPS (required for mic access)');
voiceBtn.innerHTML = `
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<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>
Voice Input
`;
voiceBtn.style.backgroundColor = '#3498db';
}
}
// Style for active nav items
const style = document.createElement('style');
style.textContent = `
.nav-item.active {
background-color: #eff6ff;
color: #2563eb;
}
.nav-item:not(.active) {
color: #374151;
}
.nav-item:not(.active):hover {
background-color: #f9fafb;
}
`;
document.head.appendChild(style);
</script>
</body>
</html>