HandicapPro v2.5 Stable Engine
Professional Turf & Dirt Analytical Intelligence (Use the Premium Past Performances from Twinspires $1.50)
Copy code from Top to Bottom
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HandicapPro | Stable Gemini 2.5 Engine</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;800;900&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #f8fafc;
color: #0f172a;
overflow-x: hidden;
}
.prose h3 {
font-size: 1.15rem;
font-weight: 900;
color: #1e293b;
margin-top: 2.5rem;
margin-bottom: 1.25rem;
border-left: 5px solid #4f46e5;
padding-left: 1rem;
text-transform: uppercase;
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(to right, #f1f5f9, transparent);
padding-top: 0.75rem;
padding-bottom: 0.75rem;
border-radius: 0 8px 8px 0;
}
.prose h4 {
font-size: 0.8rem;
font-weight: 800;
color: #4f46e5;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.075em;
background: #eef2ff;
padding: 4px 12px;
border-radius: 6px;
display: inline-flex;
align-items: center;
gap: 6px;
}
.prose p { margin-bottom: 1rem; line-height: 1.7; color: #475569; font-size: 0.95rem; }
.prose ul { list-style-type: none; padding-left: 0; margin-bottom: 2rem; }
.prose li {
margin-bottom: 0.6rem;
padding: 12px 16px;
background: #ffffff;
border-radius: 12px;
border: 1px solid #e2e8f0;
font-size: 0.9rem;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 1px 2px rgba(0,0,0,0.02);
}
.prose li:hover {
border-color: #6366f1;
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
}
.confidence-badge {
font-size: 0.7rem;
padding: 4px 12px;
border-radius: 99px;
font-weight: 900;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
letter-spacing: 0.05em;
}
@keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
.animate-slide-up { animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
.custom-scrollbar::-webkit-scrollbar { width: 5px; }
.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
.drop-zone-active {
border-color: #4f46e5 !important;
background-color: #f5f7ff !important;
transform: scale(1.01);
}
.glass-panel {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.loading-ring {
width: 80px;
height: 80px;
border-radius: 50%;
border: 4px solid #eef2ff;
border-top-color: #4f46e5;
animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body class="min-h-screen pb-12">
<div class="max-w-7xl mx-auto p-4 lg:p-10">
<!-- Brand Header -->
<header class="flex flex-col md:flex-row md:items-center justify-between mb-10 gap-6 glass-panel p-8 rounded-[2.5rem] shadow-sm border border-slate-200">
<div>
<div class="flex items-center gap-4 mb-2">
<div class="bg-indigo-600 p-3 rounded-2xl shadow-xl rotate-3">
<i data-lucide="sparkles" class="text-white w-6 h-6"></i>
</div>
<div>
<h1 class="text-3xl font-black tracking-tighter text-slate-900 uppercase leading-none">Handicap<span class="text-indigo-600">Pro</span></h1>
<span class="text-[10px] bg-indigo-50 text-indigo-600 px-2 py-0.5 rounded font-black tracking-widest uppercase border border-indigo-100">v2.5 Stable Engine</span>
</div>
</div>
<p class="text-slate-400 text-[11px] font-bold uppercase tracking-widest ml-1">Professional Turf & Dirt Analytical Intelligence</p>
</div>
<div class="flex items-center gap-3">
<div class="hidden md:flex flex-col items-end mr-4">
<span class="text-[10px] font-black text-slate-400 uppercase tracking-tighter">Engine Latency</span>
<span class="text-xs font-bold text-emerald-500 flex items-center gap-1">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span> 142ms / STABLE
</span>
</div>
<button id="reset-engine-btn" class="flex items-center gap-2 px-6 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl text-[10px] font-black text-slate-600 uppercase hover:bg-rose-50 hover:text-rose-500 hover:border-rose-200 transition-all shadow-sm group">
<i data-lucide="refresh-cw" class="w-4 h-4 transition-transform group-hover:rotate-180"></i>
Purge Session
</button>
</div>
</header>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
<!-- Controls Sidebar -->
<aside class="lg:col-span-4 space-y-6">
<!-- Data Input Card -->
<div class="bg-white rounded-[2.5rem] p-8 shadow-sm border border-slate-200">
<h2 class="text-[11px] font-black uppercase tracking-[0.2em] text-slate-400 flex items-center gap-2 mb-6">
<i data-lucide="database" class="w-4 h-4 text-indigo-500"></i> Data Acquisition
</h2>
<div id="drop-zone" class="relative group border-2 border-dashed border-slate-200 rounded-3xl p-10 transition-all hover:border-indigo-400 bg-slate-50 text-center cursor-pointer mb-8">
<input type="file" id="pdf-upload" accept=".pdf" class="hidden">
<label for="pdf-upload" class="cursor-pointer">
<div class="w-16 h-16 bg-white rounded-2xl flex items-center justify-center mx-auto mb-5 shadow-sm group-hover:scale-110 transition-transform group-hover:shadow-indigo-100 group-hover:shadow-lg">
<i data-lucide="file-text" class="w-8 h-8 text-indigo-500"></i>
</div>
<p id="file-name" class="text-sm font-black text-slate-800 truncate px-2">Upload Race Card</p>
<p class="text-[10px] text-slate-400 mt-2 uppercase font-bold tracking-wider leading-tight">Drag & Drop Form PDF<br> (DRF / Equibase / Track)</p>
</label>
</div>
<!-- Filter Scratches -->
<div class="pt-8 border-t border-slate-100">
<h2 class="text-[11px] font-black uppercase tracking-[0.2em] text-slate-400 flex items-center gap-2 mb-4">
<i data-lucide="scissors" class="w-4 h-4 text-rose-500"></i> Late Scratches
</h2>
<div class="flex gap-2 mb-4">
<input type="text" id="scr-input" placeholder="Horse # or Name" class="flex-1 bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 text-xs font-bold focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 outline-none transition-all placeholder:text-slate-300">
<button id="add-scr" class="px-5 bg-slate-900 text-white rounded-xl hover:bg-black transition-all font-black text-[10px] active:scale-95 uppercase tracking-widest">ADD</button>
</div>
<div id="scr-list" class="flex flex-wrap gap-2">
<!-- Scratch items inserted here -->
</div>
</div>
</div>
<!-- Scope Selection -->
<div class="bg-white rounded-[2.5rem] p-8 shadow-sm border border-slate-200">
<h2 class="text-[11px] font-black uppercase tracking-[0.2em] text-slate-400 flex items-center gap-2 mb-6">
<i data-lucide="map" class="w-4 h-4 text-emerald-500"></i> Analysis Scope
</h2>
<div id="race-grid" class="grid grid-cols-4 gap-3">
<!-- Race buttons generated here -->
</div>
<div class="mt-6 p-5 bg-indigo-50 rounded-2xl border border-indigo-100 flex gap-4">
<i data-lucide="zap" class="w-5 h-5 text-indigo-600 shrink-0"></i>
<p class="text-[11px] text-indigo-700/80 font-bold leading-relaxed">
Flash 2.5 uses OCR vision to map speed figures across varied track conditions. Ensure PDF text is selectable for best results.
</p>
</div>
</div>
<!-- Action Button -->
<button id="analyze-btn" disabled class="w-full py-7 rounded-[2.5rem] bg-slate-200 text-slate-400 font-black uppercase tracking-[0.3em] shadow-lg flex items-center justify-center gap-3 transition-all cursor-not-allowed group relative overflow-hidden">
<i data-lucide="brain-circuit" class="w-6 h-6 group-enabled:text-white"></i>
<span class="relative z-10">Generate Report</span>
</button>
</aside>
<!-- Results View -->
<main class="lg:col-span-8 flex flex-col min-h-[800px]">
<!-- Error Interface -->
<div id="error-box" class="hidden mb-6 bg-rose-50 border border-rose-200 p-6 rounded-3xl flex items-start gap-4 animate-slide-up">
<div class="bg-rose-500 p-2.5 rounded-xl shadow-md">
<i data-lucide="alert-octagon" class="w-5 h-5 text-white"></i>
</div>
<div class="flex-1">
<p class="text-xs font-black text-rose-800 uppercase mb-1 tracking-widest">Processing Exception</p>
<p id="error-msg" class="text-xs text-rose-600/90 leading-relaxed font-bold"></p>
</div>
<button onclick="el('error-box').classList.add('hidden')" class="text-rose-400 hover:text-rose-600"><i data-lucide="x" class="w-5 h-5"></i></button>
</div>
<!-- Empty State -->
<div id="empty-state" class="flex-1 bg-white rounded-[3rem] border border-slate-200 flex flex-col items-center justify-center text-center p-16 shadow-sm">
<div class="relative mb-10">
<div class="absolute -inset-8 bg-indigo-50 rounded-full animate-pulse opacity-40"></div>
<div class="relative w-28 h-28 bg-indigo-50 rounded-[2.5rem] flex items-center justify-center border-2 border-indigo-100 rotate-6">
<i data-lucide="binoculars" class="w-14 h-14 text-indigo-500 -rotate-6"></i>
</div>
</div>
<h3 class="text-2xl font-black text-slate-800 uppercase tracking-tight mb-4">No Active Analysis</h3>
<p class="text-slate-500 text-sm max-w-sm font-bold leading-loose mb-8">
Upload a Daily Racing Form (PDF) to initiate the Gemini 2.5 intelligence engine. We'll identify key contenders, pace scenarios, and hidden class signals.
</p>
<div class="flex flex-wrap justify-center gap-3">
<div class="px-5 py-2.5 bg-slate-50 rounded-full text-[10px] font-black text-slate-400 border border-slate-100 uppercase tracking-widest">OCR Vision Active</div>
<div class="px-5 py-2.5 bg-slate-50 rounded-full text-[10px] font-black text-slate-400 border border-slate-100 uppercase tracking-widest">Class Analysis</div>
<div class="px-5 py-2.5 bg-slate-50 rounded-full text-[10px] font-black text-slate-400 border border-slate-100 uppercase tracking-widest">Pace Map</div>
</div>
</div>
<!-- Loading State -->
<div id="loading-state" class="hidden flex-1 bg-white rounded-[3rem] border border-slate-200 p-16 text-center flex flex-col items-center justify-center shadow-inner">
<div class="loading-ring mb-10"></div>
<p id="loading-text" class="text-xs font-black text-indigo-600 uppercase tracking-[0.4em] animate-pulse">Initializing Neural Core...</p>
<p class="text-slate-400 text-[10px] mt-6 font-bold uppercase tracking-widest italic opacity-60">Handicapping complexity: O(n log n)</p>
</div>
<!-- Results Interface -->
<div id="results-state" class="hidden flex-1 flex flex-col bg-slate-50 rounded-[3rem] border border-slate-200 shadow-2xl overflow-hidden animate-slide-up">
<div class="bg-white border-b border-slate-200 p-8 flex items-center justify-between">
<div class="flex items-center gap-5">
<div class="bg-emerald-500 p-3 rounded-2xl shadow-lg shadow-emerald-100">
<i data-lucide="scroll-text" class="w-5 h-5 text-white"></i>
</div>
<div>
<h2 class="text-base font-black text-slate-900 uppercase tracking-tighter">Analysis Terminal</h2>
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.15em]">Gemini 2.5 Flash Result Output</p>
</div>
</div>
<button id="download-txt" class="flex items-center gap-3 px-6 py-3.5 bg-indigo-600 text-white rounded-2xl text-[10px] font-black uppercase hover:bg-indigo-700 transition-all shadow-xl shadow-indigo-100 active:scale-95">
<i data-lucide="copy" class="w-4 h-4"></i> Copy Report
</button>
</div>
<div class="flex-1 p-8 lg:p-12 overflow-y-auto custom-scrollbar">
<div id="analysis-content" class="prose max-w-none bg-white p-10 lg:p-16 rounded-[3rem] shadow-sm border border-slate-100 min-h-full">
<!-- AI Content -->
</div>
<div class="mt-10 text-center pb-10">
<p class="text-[9px] text-slate-300 font-black uppercase tracking-[0.4em]">Proprietary HandicapPro Engine 2024 • Verified Stable</p>
</div>
</div>
</div>
</main>
</div>
</div>
<script>
const apiKey = "";
const modelId = "gemini-2.5-flash-preview-09-2025";
let pdfBase64 = null;
let currentRace = "ALL";
let scratchedHorses = [];
let rawReport = "";
const el = (id) => document.getElementById(id);
const refreshIcons = () => lucide.createIcons();
refreshIcons();
// Engine Reset
const resetEngine = () => {
pdfBase64 = null;
scratchedHorses = [];
currentRace = "ALL";
rawReport = "";
el('file-name').textContent = "Upload Race Card";
el('pdf-upload').value = "";
el('error-box').classList.add('hidden');
el('results-state').classList.add('hidden');
el('loading-state').classList.add('hidden');
el('empty-state').classList.remove('hidden');
renderScratches();
renderRaces();
checkReadiness();
};
el('reset-engine-btn').onclick = resetEngine;
// Scratch Logic
const renderScratches = () => {
const list = el('scr-list');
list.innerHTML = '';
scratchedHorses.forEach(h => {
const tag = document.createElement('div');
tag.className = "bg-white px-4 py-2 rounded-xl text-[10px] font-black text-slate-700 border border-slate-200 flex items-center gap-3 shadow-sm animate-slide-up group";
tag.innerHTML = `<span>#${h}</span><button class="text-slate-300 hover:text-rose-500 transition-colors"><i data-lucide="x-circle" class="w-4 h-4"></i></button>`;
tag.querySelector('button').onclick = () => {
scratchedHorses = scratchedHorses.filter(item => item !== h);
renderScratches();
};
list.appendChild(tag);
});
refreshIcons();
};
el('add-scr').onclick = () => {
const val = el('scr-input').value.trim();
if (val && !scratchedHorses.includes(val)) {
scratchedHorses.push(val);
el('scr-input').value = '';
renderScratches();
}
};
// Race Grid Logic
const racesArr = ["ALL", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
const renderRaces = () => {
const grid = el('race-grid');
grid.innerHTML = '';
racesArr.forEach(r => {
const btn = document.createElement('button');
btn.className = `py-3.5 text-[10px] font-black rounded-xl border transition-all ${currentRace === r ? 'bg-indigo-600 text-white border-indigo-700 shadow-lg scale-105 z-10' : 'bg-white text-slate-500 border-slate-200 hover:bg-slate-50'}`;
btn.textContent = r === "ALL" ? "FULL CARD" : `R${r}`;
btn.onclick = () => { currentRace = r; renderRaces(); };
grid.appendChild(btn);
});
};
renderRaces();
// Drop Zone Logic
const dropZone = el('drop-zone');
['dragenter', 'dragover'].forEach(name => {
dropZone.addEventListener(name, (e) => {
e.preventDefault();
dropZone.classList.add('drop-zone-active');
});
});
['dragleave', 'drop'].forEach(name => {
dropZone.addEventListener(name, (e) => {
e.preventDefault();
dropZone.classList.remove('drop-zone-active');
});
});
dropZone.addEventListener('drop', (e) => {
const files = e.dataTransfer.files;
if (files.length) handleFile(files[0]);
});
const checkReadiness = () => {
const btn = el('analyze-btn');
if (pdfBase64) {
btn.disabled = false;
btn.className = "w-full py-7 rounded-[2.5rem] bg-indigo-600 text-white font-black uppercase tracking-[0.3em] shadow-2xl shadow-indigo-200 hover:bg-indigo-700 hover:-translate-y-1 transition-all cursor-pointer group active:scale-[0.98]";
} else {
btn.disabled = true;
btn.className = "w-full py-7 rounded-[2.5rem] bg-slate-200 text-slate-400 font-black uppercase tracking-[0.3em] shadow-lg flex items-center justify-center gap-3 transition-all cursor-not-allowed group opacity-60";
}
};
const handleFile = (file) => {
if (file && file.type === "application/pdf") {
el('file-name').textContent = file.name;
const reader = new FileReader();
reader.onload = () => {
pdfBase64 = reader.result.split(',')[1];
checkReadiness();
};
reader.readAsDataURL(file);
}
};
el('pdf-upload').onchange = (e) => handleFile(e.target.files[0]);
// Main Analysis Call
el('analyze-btn').onclick = async () => {
el('empty-state').classList.add('hidden');
el('results-state').classList.add('hidden');
el('error-box').classList.add('hidden');
el('loading-state').classList.remove('hidden');
// Dynamic Loading Text Cycle
const loadingPhrases = [
"Scanning PDF Layers...",
"Mapping Class Variants...",
"Running Pace Simulations...",
"Calibrating Speed Figures...",
"Identifying Lone Speed...",
"Extracting Form Cycles..."
];
let phraseIdx = 0;
const phraseInterval = setInterval(() => {
el('loading-text').textContent = loadingPhrases[phraseIdx];
phraseIdx = (phraseIdx + 1) % loadingPhrases.length;
}, 2500);
const scratchText = scratchedHorses.length ? `EXCLUSION LIST (SCRATCHED): Horse numbers ${scratchedHorses.join(', ')}. Do not select these as contenders.` : "";
const raceFilter = currentRace === "ALL" ? "every race found in this document" : `Race ${currentRace}`;
const prompt = `EXPERT THOROUGHBRED HANDICAPPING PROTOCOL V2.5:
Scope: Analyze ${raceFilter}.
Task: Synthesize past performances into an actionable betting report.
Key metrics to weigh:
1. Speed Figures (Beyer/Equibase)
2. Class (Moving up, moving down, or lateral)
3. Pace Projection (E, E/P, P, S roles)
4. Hidden Form (Trouble in last race, wide trips, track bias)
OUTPUT FORMAT (MANDATORY MARKDOWN):
### RACE [Number] | [Track/Level Details] [CONFIDENCE: %]
#### 🏆 TOP 3 CONTENDERS
- **[Program #] [Horse Name]:** [Projected Role, e.g., 'Lone Speed' or 'Stalking Threat'] ([Score 1-100])
#### 📊 STABLE ANALYSIS
- **Class Dynamic:** [Analysis of field class vs. contenders]
- **Pace Scenario:** [Projected pace fractions and intensity]
- **The "Look":** [Specific reason why the top pick wins, or a longshot to include in underneath bets]
${scratchText}
NOTE: If certain pages are unclear, use surrounding data context. If a race is missing, skip it. Be decisive and professional.`;
try {
const responseText = await callGeminiAI(prompt);
clearInterval(phraseInterval);
rawReport = responseText;
displayReport(responseText);
el('loading-state').classList.add('hidden');
el('results-state').classList.remove('hidden');
} catch (err) {
clearInterval(phraseInterval);
el('loading-state').classList.add('hidden');
el('error-box').classList.remove('hidden');
el('error-msg').textContent = err.message;
}
};
async function callGeminiAI(prompt) {
const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:generateContent?key=${apiKey}`;
let retryCount = 0;
const maxRetries = 5;
const executeFetch = async () => {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{
parts: [
{ text: prompt },
{ inlineData: { mimeType: "application/pdf", data: pdfBase64 } }
]
}]
})
});
if (!res.ok) {
if (retryCount < maxRetries) {
retryCount++;
const delay = Math.pow(2, retryCount) * 1000;
await new Promise(r => setTimeout(r, delay));
return executeFetch();
}
const errorData = await res.json();
throw new Error(errorData.error?.message || "Internal engine fault.");
}
const data = await res.json();
if (!data.candidates?.[0]?.content?.parts?.[0]?.text) {
throw new Error("Empty response. PDF might be encrypted or too dense.");
}
return data.candidates[0].content.parts[0].text;
};
return executeFetch();
}
const displayReport = (text) => {
const box = el('analysis-content');
box.innerHTML = '';
const lines = text.split('\n');
lines.forEach(line => {
const tl = line.trim();
if (!tl) return;
if (tl.startsWith('###')) {
const h3 = document.createElement('h3');
let rawText = tl.replace(/###/g, '').trim();
const confMatch = rawText.match(/\[CONFIDENCE:\s*(\d+%|.*?)\]/i);
if (confMatch) {
const titleOnly = rawText.split('[')[0].trim();
const percent = confMatch[1];
let cClass = "bg-indigo-100 text-indigo-700";
const n = parseInt(percent);
if (n >= 85) cClass = "bg-emerald-100 text-emerald-700";
else if (n <= 55) cClass = "bg-rose-100 text-rose-700";
h3.innerHTML = `<span>${titleOnly}</span> <span class="confidence-badge ${cClass}">${percent} Confidence</span>`;
} else {
h3.textContent = rawText;
}
box.appendChild(h3);
}
else if (tl.startsWith('####')) {
const h4 = document.createElement('h4');
const textContent = tl.replace(/####/g, '').trim();
// Add icons to subheaders
let icon = 'chevron-right';
if (textContent.includes('CONTENDERS')) icon = 'trophy';
if (textContent.includes('STABLE')) icon = 'bar-chart-3';
h4.innerHTML = `<i data-lucide="${icon}" class="w-3.5 h-3.5"></i> ${textContent}`;
box.appendChild(h4);
}
else if (tl.startsWith('*') || tl.startsWith('-')) {
const li = document.createElement('li');
li.innerHTML = tl.replace(/^[\*\-]/, '').trim()
.replace(/\*\*(.*?)\*\*/g, '<strong class="text-indigo-600">$1</strong>');
let ul = box.lastElementChild;
if (!ul || ul.tagName !== 'UL') {
ul = document.createElement('ul');
box.appendChild(ul);
}
ul.appendChild(li);
} else {
const p = document.createElement('p');
p.innerHTML = tl.replace(/\*\*(.*?)\*\*/g, '<strong class="text-slate-900">$1</strong>');
box.appendChild(p);
}
});
refreshIcons();
};
el('download-txt').onclick = () => {
const textArea = document.createElement("textarea");
textArea.value = rawReport;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
el('download-txt').innerHTML = '<i data-lucide="check" class="w-4 h-4"></i> Copied!';
setTimeout(() => {
el('download-txt').innerHTML = '<i data-lucide="copy" class="w-4 h-4"></i> Copy Report';
refreshIcons();
}, 2000);
} catch (err) {
console.error('Copy failed');
}
document.body.removeChild(textArea);
};
</script>
</body>
</html>