stp / index.html
MarcRyan's picture
Add 3 files
4637fbe verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Soccer Team Performance Predictor</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/brain/0.6.3/brain.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.neuron {
transition: all 0.3s ease;
}
.neuron:hover {
transform: scale(1.1);
}
.network-container {
perspective: 1000px;
}
.layer {
transform-style: preserve-3d;
}
.connection {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: draw 1.5s forwards;
}
@keyframes draw {
to {
stroke-dashoffset: 0;
}
}
.progress-ring__circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.team-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.team-card {
transition: all 0.3s ease;
}
.soccer-pitch {
background: linear-gradient(to bottom, #4ade80, #22c55e);
position: relative;
overflow: hidden;
}
.soccer-pitch::before {
content: "";
position: absolute;
top: 0;
left: 50%;
width: 2px;
height: 100%;
background: white;
transform: translateX(-50%);
}
.soccer-pitch::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 100px;
height: 100px;
border: 2px solid white;
border-radius: 50%;
transform: translate(-50%, -50%);
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-12">
<h1 class="text-4xl font-bold text-gray-800 mb-2">⚽ Soccer Team Performance Predictor</h1>
<p class="text-gray-600 max-w-2xl mx-auto">A neural network that predicts team loss probability based on key soccer performance metrics</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Configuration Panel -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Network Configuration</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Hidden Layers</label>
<input type="range" id="hiddenLayersInput" min="1" max="5" value="3" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-500">
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
<span>5</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Neurons per Layer</label>
<input type="range" id="neuronsPerLayerInput" min="3" max="10" value="6" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-500">
<span>3</span>
<span>4</span>
<span>5</span>
<span>6</span>
<span>7</span>
<span>8</span>
<span>9</span>
<span>10</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Learning Rate</label>
<input type="range" id="learningRateInput" min="0.01" max="0.5" step="0.01" value="0.2" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-500">
<span>0.01</span>
<span>0.25</span>
<span>0.5</span>
</div>
</div>
<div class="flex space-x-3 pt-2">
<button id="trainBtn" class="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-lg flex items-center justify-center">
<i class="fas fa-brain mr-2"></i> Train Network
</button>
<button id="resetBtn" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-lg flex items-center justify-center">
<i class="fas fa-redo mr-2"></i> Reset
</button>
</div>
</div>
<div class="mt-6">
<h3 class="text-sm font-medium text-gray-700 mb-2">Training Status</h3>
<div id="trainingStatus" class="text-sm text-gray-600 bg-gray-100 p-3 rounded-lg">
Network not trained yet
</div>
</div>
</div>
<!-- Network Visualization -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Network Architecture</h2>
<div id="networkVisualization" class="network-container h-64 flex justify-center items-center">
<svg id="networkSvg" width="100%" height="100%" viewBox="0 0 500 300"></svg>
</div>
<div class="mt-6">
<h3 class="text-sm font-medium text-gray-700 mb-2">Error Over Time</h3>
<div class="bg-gray-100 p-2 rounded-lg">
<canvas id="errorChart" height="150"></canvas>
</div>
</div>
</div>
<!-- Prediction Panel -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Make a Prediction</h2>
<div class="space-y-3">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Goals Conceded per Game</label>
<input id="goalsConcededInput" type="number" step="0.1" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500" placeholder="1.2">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Shots on Target per Game</label>
<input id="shotsOnTargetInput" type="number" step="0.1" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500" placeholder="5.5">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Pass Accuracy %</label>
<input id="passAccuracyInput" type="number" step="0.1" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500" placeholder="85.0">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Tackles per Game</label>
<input id="tacklesInput" type="number" step="0.1" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500" placeholder="15.0">
</div>
<button id="predictBtn" class="w-full bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-lg mt-4 flex items-center justify-center">
<i class="fas fa-calculator mr-2"></i> Predict Loss Probability
</button>
<div id="predictionResult" class="mt-4 p-4 rounded-lg bg-green-50 hidden">
<h4 class="font-medium text-green-800 mb-1">Prediction Result</h4>
<p id="predictionText" class="text-green-600"></p>
<div class="mt-3 flex items-center">
<div class="w-12 h-12 mr-3">
<svg class="progress-ring" width="48" height="48">
<circle class="progress-ring__circle" stroke="#E5E7EB" stroke-width="4" fill="transparent" r="20" cx="24" cy="24"/>
<circle class="progress-ring__circle" stroke="#10B981" stroke-width="4" fill="transparent" r="20" cx="24" cy="24"/>
</svg>
</div>
<div class="text-2xl font-bold text-green-600" id="predictionPercentage">0%</div>
</div>
</div>
</div>
</div>
</div>
<!-- Team Cards -->
<div class="mt-12">
<h2 class="text-2xl font-bold text-gray-800 mb-6">Top European Clubs</h2>
<div id="teamPredictions" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"></div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Configuration
const config = {
hiddenLayers: 3,
neuronsPerLayer: 6,
learningRate: 0.2,
iterations: 20000,
errorThresh: 0.005
};
// Sample soccer teams data
let soccerTeams = [
{ name: "Manchester City", logo: "🔵", goalsConceded: 0.8, shotsOnTarget: 6.7, passAccuracy: 89.2, tackles: 12.3 },
{ name: "Real Madrid", logo: "⚪", goalsConceded: 1.1, shotsOnTarget: 5.9, passAccuracy: 87.5, tackles: 14.2 },
{ name: "Bayern Munich", logo: "🔴", goalsConceded: 0.9, shotsOnTarget: 6.3, passAccuracy: 88.1, tackles: 13.7 },
{ name: "Liverpool", logo: "🔴", goalsConceded: 1.2, shotsOnTarget: 6.1, passAccuracy: 85.7, tackles: 15.8 },
{ name: "Paris Saint-Germain", logo: "🔵", goalsConceded: 1.0, shotsOnTarget: 5.8, passAccuracy: 86.9, tackles: 11.9 },
{ name: "Barcelona", logo: "🔵🔴", goalsConceded: 1.3, shotsOnTarget: 5.5, passAccuracy: 87.8, tackles: 12.5 },
{ name: "Chelsea", logo: "🔵", goalsConceded: 1.4, shotsOnTarget: 4.9, passAccuracy: 84.3, tackles: 16.2 },
{ name: "AC Milan", logo: "⚫🔴", goalsConceded: 1.2, shotsOnTarget: 4.7, passAccuracy: 83.5, tackles: 14.9 }
];
// Initialize network
let net = new brain.NeuralNetwork();
let errorChart;
let trainingData = generateTrainingData();
// DOM Elements
const hiddenLayersInput = document.getElementById('hiddenLayersInput');
const neuronsPerLayerInput = document.getElementById('neuronsPerLayerInput');
const learningRateInput = document.getElementById('learningRateInput');
const trainBtn = document.getElementById('trainBtn');
const resetBtn = document.getElementById('resetBtn');
const goalsConcededInput = document.getElementById('goalsConcededInput');
const shotsOnTargetInput = document.getElementById('shotsOnTargetInput');
const passAccuracyInput = document.getElementById('passAccuracyInput');
const tacklesInput = document.getElementById('tacklesInput');
const predictBtn = document.getElementById('predictBtn');
const predictionResult = document.getElementById('predictionResult');
const predictionText = document.getElementById('predictionText');
const predictionPercentage = document.getElementById('predictionPercentage');
const teamPredictions = document.getElementById('teamPredictions');
const trainingStatus = document.getElementById('trainingStatus');
// Initialize UI
initUI();
drawNetwork();
initChart();
renderTeamCards();
// Event Listeners
hiddenLayersInput.addEventListener('input', updateConfig);
neuronsPerLayerInput.addEventListener('input', updateConfig);
learningRateInput.addEventListener('input', updateConfig);
trainBtn.addEventListener('click', trainNetwork);
resetBtn.addEventListener('click', resetNetwork);
predictBtn.addEventListener('click', predictLossProbability);
// Functions
function generateTrainingData() {
// Generate synthetic training data based on realistic soccer stats
const data = [];
for (let i = 0; i < 100; i++) {
// Generate random but realistic soccer stats
const goalsConceded = 0.5 + Math.random() * 2.5; // Between 0.5 and 3.0
const shotsOnTarget = 2.0 + Math.random() * 6.0; // Between 2.0 and 8.0
const passAccuracy = 70.0 + Math.random() * 25.0; // Between 70% and 95%
const tackles = 8.0 + Math.random() * 12.0; // Between 8 and 20
// Calculate a synthetic loss probability based on these stats
// Better stats (fewer goals conceded, more shots on target, higher pass accuracy, more tackles)
// should lead to lower loss probability
let lossProbability =
(goalsConceded / 3.0) * 0.4 + // Goals conceded contributes 40%
((8.0 - shotsOnTarget) / 6.0) * 0.3 + // Shots on target contributes 30%
((95.0 - passAccuracy) / 25.0) * 0.2 + // Pass accuracy contributes 20%
((20.0 - tackles) / 12.0) * 0.1; // Tackles contributes 10%
// Add some randomness
lossProbability += (Math.random() - 0.5) * 0.1;
// Ensure between 0 and 1
lossProbability = Math.max(0, Math.min(1, lossProbability));
data.push({
input: {
goalsConceded: normalize(goalsConceded, 0.5, 3.0),
shotsOnTarget: normalize(shotsOnTarget, 2.0, 8.0),
passAccuracy: normalize(passAccuracy, 70.0, 95.0),
tackles: normalize(tackles, 8.0, 20.0)
},
output: {
loss: lossProbability
}
});
}
return data;
}
function normalize(value, min, max) {
return (value - min) / (max - min);
}
function denormalize(value, min, max) {
return value * (max - min) + min;
}
function initUI() {
// Set initial values from config
hiddenLayersInput.value = config.hiddenLayers;
neuronsPerLayerInput.value = config.neuronsPerLayer;
learningRateInput.value = config.learningRate;
// Initialize prediction progress ring
const circle = document.querySelector('.progress-ring__circle:last-child');
const radius = circle.r.baseVal.value;
const circumference = radius * 2 * Math.PI;
circle.style.strokeDasharray = circumference;
circle.style.strokeDashoffset = circumference;
}
function updateConfig() {
config.hiddenLayers = parseInt(hiddenLayersInput.value);
config.neuronsPerLayer = parseInt(neuronsPerLayerInput.value);
config.learningRate = parseFloat(learningRateInput.value);
drawNetwork();
}
function resetNetwork() {
net = new brain.NeuralNetwork();
trainingStatus.textContent = "Network reset - not trained";
trainingStatus.className = "text-sm text-gray-600 bg-gray-100 p-3 rounded-lg";
// Reset chart
if (errorChart) {
errorChart.data.labels = [];
errorChart.data.datasets[0].data = [];
errorChart.update();
}
// Hide prediction result
predictionResult.classList.add('hidden');
}
function trainNetwork() {
trainBtn.disabled = true;
trainBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Training...';
trainingStatus.textContent = "Training in progress...";
trainingStatus.className = "text-sm text-green-600 bg-green-100 p-3 rounded-lg";
// Configure network
net = new brain.NeuralNetwork({
hiddenLayers: [Array(config.hiddenLayers).fill(config.neuronsPerLayer)].flat(),
learningRate: config.learningRate,
iterations: config.iterations,
errorThresh: config.errorThresh,
log: true,
logPeriod: 1000,
callback: function(info) {
updateTrainingStatus(info.iterations, info.error);
updateChart(info.iterations, info.error);
},
callbackPeriod: 1000
});
// Train in a timeout to allow UI to update
setTimeout(() => {
net.train(trainingData, (err, info) => {
trainBtn.disabled = false;
trainBtn.innerHTML = '<i class="fas fa-brain mr-2"></i> Train Network';
if (err) {
trainingStatus.textContent = "Training failed: " + err;
trainingStatus.className = "text-sm text-red-600 bg-red-100 p-3 rounded-lg";
} else {
trainingStatus.textContent = `Training complete! Final error: ${info.error.toFixed(6)} after ${info.iterations} iterations`;
trainingStatus.className = "text-sm text-green-600 bg-green-100 p-3 rounded-lg";
}
// Animate network connections
animateNetwork();
});
}, 100);
}
function updateTrainingStatus(iterations, error) {
trainingStatus.textContent = `Training... Iteration: ${iterations}, Error: ${error.toFixed(6)}`;
}
function initChart() {
const ctx = document.getElementById('errorChart').getContext('2d');
errorChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Training Error',
data: [],
borderColor: 'rgb(16, 185, 129)',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
borderWidth: 2,
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Error'
}
},
x: {
title: {
display: true,
text: 'Iterations'
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return `Error: ${context.parsed.y.toFixed(6)}`;
}
}
}
}
}
});
}
function updateChart(iteration, error) {
errorChart.data.labels.push(iteration);
errorChart.data.datasets[0].data.push(error);
errorChart.update();
}
function drawNetwork() {
const svg = document.getElementById('networkSvg');
svg.innerHTML = '';
const width = 500;
const height = 300;
const layerCount = config.hiddenLayers + 2; // Input + hidden + output
const neuronRadius = 15;
// Draw layers
for (let layer = 0; layer < layerCount; layer++) {
const isInput = layer === 0;
const isOutput = layer === layerCount - 1;
let neuronCount;
if (isInput) neuronCount = 4; // 4 input features
else if (isOutput) neuronCount = 1; // 1 output (loss probability)
else neuronCount = config.neuronsPerLayer;
const layerX = 50 + (width - 100) * (layer / (layerCount - 1));
// Draw neurons
for (let n = 0; n < neuronCount; n++) {
const neuronY = height / 2 + (n - (neuronCount - 1) / 2) * 40;
// Neuron circle
const neuron = document.createElementNS("http://www.w3.org/2000/svg", "circle");
neuron.setAttribute("cx", layerX);
neuron.setAttribute("cy", neuronY);
neuron.setAttribute("r", neuronRadius);
neuron.setAttribute("class", "neuron");
if (isInput) {
neuron.setAttribute("fill", "#4ADE80"); // Green for input
} else if (isOutput) {
neuron.setAttribute("fill", "#3B82F6"); // Blue for output
} else {
neuron.setAttribute("fill", "#F59E0B"); // Yellow for hidden
}
svg.appendChild(neuron);
// Neuron label
if (isInput) {
const labels = ["Goals Conceded", "Shots on Target", "Pass %", "Tackles"];
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("x", layerX - 50);
text.setAttribute("y", neuronY + 5);
text.setAttribute("text-anchor", "end");
text.setAttribute("class", "text-xs font-medium fill-gray-700");
text.textContent = labels[n];
svg.appendChild(text);
} else if (isOutput) {
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("x", layerX + 50);
text.setAttribute("y", neuronY + 5);
text.setAttribute("text-anchor", "start");
text.setAttribute("class", "text-xs font-medium fill-gray-700");
text.textContent = "Loss %";
svg.appendChild(text);
}
}
}
}
function animateNetwork() {
const svg = document.getElementById('networkSvg');
const connections = [];
const width = 500;
const height = 300;
const layerCount = config.hiddenLayers + 2;
const neuronRadius = 15;
// Create connections between layers
for (let layer = 0; layer < layerCount - 1; layer++) {
const isFirstLayer = layer === 0;
const isLastLayer = layer === layerCount - 2;
let fromNeuronCount;
if (isFirstLayer) fromNeuronCount = 4;
else fromNeuronCount = config.neuronsPerLayer;
let toNeuronCount;
if (isLastLayer) toNeuronCount = 1;
else toNeuronCount = config.neuronsPerLayer;
const fromLayerX = 50 + (width - 100) * (layer / (layerCount - 1));
const toLayerX = 50 + (width - 100) * ((layer + 1) / (layerCount - 1));
for (let from = 0; from < fromNeuronCount; from++) {
const fromY = height / 2 + (from - (fromNeuronCount - 1) / 2) * 40;
for (let to = 0; to < toNeuronCount; to++) {
const toY = height / 2 + (to - (toNeuronCount - 1) / 2) * 40;
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute("x1", fromLayerX + neuronRadius);
line.setAttribute("y1", fromY);
line.setAttribute("x2", toLayerX - neuronRadius);
line.setAttribute("y2", toY);
line.setAttribute("stroke", "#9CA3AF");
line.setAttribute("stroke-width", "1");
line.setAttribute("class", "connection");
svg.appendChild(line);
connections.push(line);
}
}
}
// Animate connections with staggered delay
connections.forEach((conn, i) => {
setTimeout(() => {
conn.style.strokeDashoffset = "0";
}, i * 20);
});
}
function predictLossProbability() {
if (!net.trainOpts) {
predictionText.textContent = "Please train the network first";
predictionResult.classList.remove('hidden');
predictionResult.className = "mt-4 p-4 rounded-lg bg-yellow-50";
return;
}
// Get input values
const goalsConceded = parseFloat(goalsConcededInput.value) || 1.2;
const shotsOnTarget = parseFloat(shotsOnTargetInput.value) || 5.5;
const passAccuracy = parseFloat(passAccuracyInput.value) || 85.0;
const tackles = parseFloat(tacklesInput.value) || 15.0;
// Normalize inputs
const normalizedInput = {
goalsConceded: normalize(goalsConceded, 0.5, 3.0),
shotsOnTarget: normalize(shotsOnTarget, 2.0, 8.0),
passAccuracy: normalize(passAccuracy, 70.0, 95.0),
tackles: normalize(tackles, 8.0, 20.0)
};
// Make prediction
const output = net.run(normalizedInput);
const lossProbability = output.loss;
const percentage = Math.round(lossProbability * 100);
// Display result
predictionText.textContent = `Based on the team's stats (Goals Conceded: ${goalsConceded.toFixed(1)}, Shots on Target: ${shotsOnTarget.toFixed(1)}, Pass Accuracy: ${passAccuracy.toFixed(1)}%, Tackles: ${tackles.toFixed(1)}), the predicted loss probability is:`;
predictionPercentage.textContent = `${percentage}%`;
// Update progress ring
const circle = document.querySelector('.progress-ring__circle:last-child');
const radius = circle.r.baseVal.value;
const circumference = radius * 2 * Math.PI;
const offset = circumference - (percentage / 100) * circumference;
circle.style.strokeDashoffset = offset;
// Set color based on probability
if (percentage > 70) {
circle.style.stroke = "#EF4444"; // red
predictionPercentage.className = "text-2xl font-bold text-red-600";
} else if (percentage > 50) {
circle.style.stroke = "#F59E0B"; // orange
predictionPercentage.className = "text-2xl font-bold text-yellow-600";
} else {
circle.style.stroke = "#10B981"; // green
predictionPercentage.className = "text-2xl font-bold text-green-600";
}
predictionResult.classList.remove('hidden');
predictionResult.className = "mt-4 p-4 rounded-lg bg-green-50";
}
function renderTeamCards() {
teamPredictions.innerHTML = '';
soccerTeams.forEach(team => {
const card = document.createElement('div');
card.className = 'bg-white rounded-lg shadow-md p-4 team-card hover:shadow-lg transition-all';
card.innerHTML = `
<div class="flex items-center mb-3">
<div class="text-2xl mr-3">${team.logo}</div>
<h3 class="font-semibold text-gray-800">${team.name}</h3>
</div>
<div class="grid grid-cols-2 gap-2 text-sm mb-3">
<div><span class="text-gray-500">Goals Conceded:</span> ${team.goalsConceded.toFixed(1)}</div>
<div><span class="text-gray-500">Shots on Target:</span> ${team.shotsOnTarget.toFixed(1)}</div>
<div><span class="text-gray-500">Pass %:</span> ${team.passAccuracy.toFixed(1)}%</div>
<div><span class="text-gray-500">Tackles:</span> ${team.tackles.toFixed(1)}</div>
</div>
<div class="flex items-center">
<button class="w-full bg-green-100 hover:bg-green-200 text-green-800 py-2 px-3 rounded flex items-center justify-center predict-btn" data-team='${JSON.stringify(team)}'>
<i class="fas fa-calculator mr-2 text-xs"></i> Predict Performance
</button>
</div>
`;
teamPredictions.appendChild(card);
});
// Add event listeners to predict buttons
document.querySelectorAll('.predict-btn').forEach(btn => {
btn.addEventListener('click', function() {
const team = JSON.parse(this.getAttribute('data-team'));
goalsConcededInput.value = team.goalsConceded;
shotsOnTargetInput.value = team.shotsOnTarget;
passAccuracyInput.value = team.passAccuracy;
tacklesInput.value = team.tackles;
if (net.trainOpts) {
predictLossProbability();
} else {
predictionText.textContent = "Please train the network first";
predictionResult.classList.remove('hidden');
predictionResult.className = "mt-4 p-4 rounded-lg bg-yellow-50";
}
});
});
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MarcRyan/stp" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>