marci / Templates /index.html
BraydenMoore's picture
Update with up to date data
3b397ee
raw
history blame
23.4 kB
<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<head>
<link rel="shortcut icon" type="image/x-icon" href="https://images.squarespace-cdn.com/content/v1/64790f5777b5d772678cce83/6d71eaee-f825-4324-be9b-2def32469eac/favicon.ico?format=100w">
<title>MARCI - NFL Betting</title>
</head>
<style>
body {
max-width: 90vw;
margin: auto;
background-color: black;
font-family: 'Helvetica';
justify-content: center;
text-align: center;
padding-top: 2%;
padding-bottom: 5%;
}
p {
color: #f2f2f2;
}
h1 {
color: #f2f2f2;
margin-top: 40px;
margin-bottom: 10px;
font-size: xxx-large;
}
h2 {
margin-top: 0px;
color: #f2f2f2;
}
h3 {
color: #f2f2f2;
margin: 0px;
}
table {
transition: 0.3s ease;
margin-top: 20px;
width: 80%;
border-collapse: collapse;
text-align: center;
}
.table-div {
display: flex;
justify-content: center;
}
th, td {
color: #f2f2f2;
border: 1px solid black;
text-align: center;
padding: 8px;
}
th {
background-color: black;
}
tr {
background-color: black;
}
tr:nth-child(even) {
background-color: rgb(10, 10, 5);
}
td img {
display: block;
margin: auto;
}
input[type="text"] {
font-size: 12pt;
width: 45px;
height: 30px;
text-align: center;
background-color: transparent;
border-radius: 5px;
transition: 0.3s ease;
color: #f2f2f2;
border: none;
}
input[type="text"]:hover {
background-color:rgb(30, 30, 30);
}
button {
font-size: 12pt;
background-color: rgb(30, 30, 30);
color: #ffffff;
padding: 10px 20px;
border: none;
border-radius: 5px;
margin-top: 40px;
width: 80%;
transition: all 0.3s ease;
}
button:hover {
color: rgb(0, 0, 0);
background-color: rgb(255, 255, 255);
cursor: pointer;
}
.winner-wrapper {
cursor: default;
position: relative;
width: 100%;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
transition: 0.3s ease;
}
.winner-image {
height: auto;
margin: 0;
transition: 0.3s ease;
}
.over-under-wrapper {
cursor: default;
position: relative;
width: 100%;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
transition: 0.3s ease;
}
.over-under-text {
display: inline-block;
margin: 0;
margin-right: 2px;
font-weight: bold;
}
.over {
color: rgb(255, 255, 255);
}
.under {
color: rgb(255, 255, 255);
}
.na {
color: white;
}
.highlight {
background: rgb(30, 30, 30) !important;
border: 2px solid rgb(30, 30, 30) !important;
border-radius: 10px !important;
}
.force-repaint { transform: translateZ(0); }
.hidden {
opacity: 0;
}
.section-container {
display: flex;
justify-content: space-between;
}
.section {
padding: 30px;
text-align: left;
border-style: solid;
border-width: 1px;
border-color: rgb(61, 61, 61);
width: 48%;
}
.content {
width: 100%;
}
.content img {
width: 100%;
height: auto;
margin-top: 20px;
margin-bottom: 20px;
}
.divider {
border: 0;
height: 1px;
background: rgb(61, 61, 61);
margin-top: 50px;
margin-bottom: 50px;
width: 80%;
}
.label {
color: rgb(114, 114, 114);
}
.info {
color: white;
}
a {
color: white;
}
.scroll-banner {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
width: 100%;
display: flex;
align-items: center;
height: 30px;
background-color: green;
overflow: hidden;
visibility: hidden;
}
.scroll-text {
font-family: 'Helvetica';
color: white;
display: inline-block;
animation: scrolling 10s linear infinite;
white-space: nowrap;
}
@keyframes scrolling {
0% { transform: translateX(100vw); }
100% { transform: translateX(-100%); }
}
.emoji {
margin-left: 5px;
color: rgb(255, 255, 255);
transition: 0.3s ease;
}
.spinner {
margin: auto;
display: block;
border: 2px solid transparent;
border-radius: 50%;
border-top: 2px solid #6a6a6a;
width: 16px;
height: 16px;
animation: spin 1s linear infinite;
}
#gradient {
background: red;
background: -webkit-linear-gradient(left, orange , yellow, green, cyan, blue, violet);
background: -o-linear-gradient(right, orange, yellow, green, cyan, blue, violet);
background: -moz-linear-gradient(right, orange, yellow, green, cyan, blue, violet);
background: linear-gradient(to right, orange , yellow, green, cyan, blue, violet);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: bold;
}
.modelDetails {
width: 80%;
display: inline-block;
margin-bottom: 40px;
}
#weekSelector {
transition: 0.3s ease;
border-radius: 10px;
padding: 5px;
color: white;
background: rgb(30, 30, 30) !important;
font-family: Arial, Helvetica, sans-serif;
}
#weekSelector:hover {
opacity: 0.5;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@media screen and (max-width: 768px) {
.table-div {
display: block;
justify-content: center;
}
.winner-image {
margin: 0;
}
.emoji {
margin: 0;
}
.table-div{
overflow-x: scroll;
}
.divider {
width: 90%;
}
#modelDetails {
width: 90%;
}
.button {
width: 90%;
}
.section-container {
display: inline;
}
.section {
padding: 15px;
width: auto;
}
}
</style>
<div class="scroll-banner">
<div class="scroll-text">
Predictions will begin at the conclusion of Week 1. Bet at your own risk. Know your limits. And most importantly, have fun!
</div>
</div>
<body>
<h1>M A R C I</h1>
<div class="info">
<span class="label"><i>Moore's Algorithm for Risky Capital Investments</i></span><br><br>
<span id="gradient">Nobody said it was easy!</span><br><br>
<span class="label"><b>Record through {{ latest_game }}</b></span><br>
<span class="label">Winners:</span> {{ winners_correct }}-{{winners_incorrect}}{{winners_tie}}<span class="label"> ({{ winners_return }})</span><br>
<span class="label">Over/Unders:</span> {{over_unders_correct}}-{{over_unders_incorrect}}{{over_unders_push}}<span class="label"> ({{over_unders_return}})</span><br><br>
</div>
<select id="weekSelector">
</select>
<div class="table-div">
<table id="gameTable">
<tr>
<th>Date</th>
<th>Away</th>
<th>Home</th>
<th>O/U</th>
<th>Predicted Winner</th>
<th>Predicted O/U</th>
</tr>
</table>
</div>
<button id="submitButton">
Predict
</button>
<hr class="divider">
<div class="modelDetails">
<h2>Model Train/Test Details</h2>
<div class="section-container">
<div class="section">
<h3>Moneyline</h3>
<div class="info"></h3><span class="label">Test Accuracy:</span> 71.4%<br></div>
<div class="content">
<img src="/Static/xgboost_ML_no_odds_71.4%25_dark.png" alt="Moneyline Model">
<div class="info">
<span class="label">Model:</span> XGBoost<br>
<span class="label">Train/Test Split:</span> 1782/199<br>
<span class="label">Max Depth:</span> 2<br>
<span class="label">Learning Rate:</span> 0.01<br>
<span class="label">Epochs:</span> 500
</div>
</div>
</div>
<div class="section">
<h3>Over/Under</h3>
<div class="content">
<div class="info"></h3><span class="label">Test Accuracy:</span> 59.8%<br></div>
<img src="/Static/xgboost_OU_no_odds_59.8%25_dark.png" alt="Over/Under Model">
<div class="info">
<span class="label">Model:</span> XGBoost<br>
<span class="label">Train/Test Split:</span> 1782/199<br>
<span class="label">Max Depth:</span> 6<br>
<span class="label">Learning Rate:</span> 0.05<br>
<span class="label">Epochs:</span> 300
</div>
</div>
</div>
</div>
</div>
<div class="modelDetails">
<h2>Winnings This Year</h2>
<div class="section-container">
<div class="section">
<h3>Moneyline</h3>
<div class="content">
<img src="/Static/Winner_Cumsum_dark.png" alt="Moneyline Model">
</div>
</div>
<div class="section">
<h3>Over/Under</h3>
<div class="content">
<img src="/Static/Over_Under_Cumsum_dark.png" alt="Over/Under Model">
</div>
</div>
</div>
</div>
<div class="modelDetails">
<h2>Predictive Accuracy This Year</h2>
<div class="section-container">
<div class="section">
<h3>Moneyline</h3>
<div class="info">{{ winners_return }}.</div>
<div class="content">
<img src="/Static/Winner_Predictions_dark.png" alt="Moneyline Accuracy">
</div>
<div class="info"><span class="label">{{ winners_binom }}</span><br></div>
</div>
<div class="section">
<h3>Over/Under</h3>
<div class="info">{{ over_unders_return }}.</div>
<div class="content">
<img src="/Static/Over_Under_Predictions_dark.png" alt="Over/Under Model">
</div>
<div class="info"><span class="label">{{ over_unders_binom }}</span><br></div>
</div>
</div>
</div>
<p>πŸ€—<a href="https://huggingface.co/spaces/BraydenMoore/MARCI-NFL-Betting/tree/main">See the Code</a></p>
<script>
async function fetchGames(selectedWeek) {
const response = await fetch(`/get_games?week=${selectedWeek}`);
const pulled_games = await response.json();
const table = document.getElementById('gameTable');
for(let i = table.rows.length - 1; i > 0; i--) {
table.deleteRow(i);
}
const columns = ['Date','Away Team', 'Home Team'];
let lines;
try {
const lines_response = await fetch('/get_lines');
if (!lines_response.ok) {
throw new Error(`HTTP error! status: ${lines_response.status}`);
}
lines = await lines_response.json();
}
catch (error) {
lines = new Array(20).fill(0);
}
pulled_games.forEach((game, index) => {
const row = table.insertRow(-1);
columns.forEach((column) => {
const cell = row.insertCell(-1);
if (column === 'Away Team' || column === 'Home Team') {
const img = document.createElement('img');
img.src = `/Static/${game[column]}.webp`;
img.alt = game[column];
img.width = 50;
cell.appendChild(img);
} else {
cell.textContent = game[column];
cell.style.color = "rgb(114, 114, 114)";
}
});
for (let i = 0; i < 3; i++) {
const cell = row.insertCell(-1);
if (i<1) {
const input = document.createElement('input');
input.type = 'text';
input.value = lines[index];
cell.appendChild(input);
}
}
});
}
function submitData() {
const predictButton = document.getElementById('submitButton');
const table = document.getElementById('gameTable');
const rows = table.querySelectorAll('tr');
const games = [];
rows.forEach((row, index) => {
if (index === 0) return;
const winnerCell = row.cells[row.cells.length - 2];
const overUnderCell = row.cells[row.cells.length - 1];
const spinnerDiv = document.createElement('div');
spinnerDiv.className = 'spinner';
winnerCell.innerHTML = '';
overUnderCell.innerHTML = '';
winnerCell.appendChild(spinnerDiv);
overUnderCell.appendChild(spinnerDiv.cloneNode(true));
const cells = row.querySelectorAll('td');
const game = {};
game.Date = cells[0].textContent;
game.AwayTeam = cells[1].querySelector('img').alt;
game.HomeTeam = cells[2].querySelector('img').alt;
game.OverUnderLine = cells[3].querySelector('input').value;
game.rowIndex = index - 1;
games.push(game);
});
fetch('/submit_games', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(games),
})
.then(response => response.json())
.then(data => {
if (data.moneylines && data.over_unders) {
const table = document.getElementById('gameTable');
const rows = table.querySelectorAll('tr');
data.moneylines.forEach((moneyline, index) => {
const row = rows[parseInt(moneyline.rowIndex) + 1];
const winnerCell = row.cells[row.cells.length - 2];
winnerCell.removeChild(winnerCell.querySelector('.spinner'));
winnerCell.innerHTML = '';
const wrapperDiv = document.createElement('div');
wrapperDiv.className = 'winner-wrapper';
if (moneyline.Probabilities[0] > 0.6){
wrapperDiv.classList.add("highlight");
}
else {
wrapperDiv.style.opacity = "0.5";
}
const winnerImg = document.createElement('img');
winnerImg.src = `/Static/${moneyline.Winner}.webp`;
winnerImg.alt = moneyline.Winner;
winnerImg.width = 50;
winnerImg.className = 'winner-image hidden';
wrapperDiv.appendChild(winnerImg);
const winnerEmojiDiv = document.createElement('div');
winnerEmojiDiv.className = 'emoji';
wrapperDiv.dataset.proba = Math.floor(moneyline.Probabilities[0] * 100).toFixed(0);
if (moneyline.Winner[0] === moneyline.Result) {
winnerEmojiDiv.textContent = 'βœ…';
}
else if (moneyline.Result === 'Tie') {
winnerEmojiDiv.textContent = 'πŸ”΅';
}
else {
winnerEmojiDiv.textContent = '❌';
}
if (moneyline.Result === 'N/A') {
winnerEmojiDiv.textContent = `(${wrapperDiv.dataset.proba}%)`;
}
wrapperDiv.appendChild(winnerEmojiDiv);
setTimeout(() => {
winnerImg.classList.remove('hidden');
}, 10);
winnerCell.appendChild(wrapperDiv);
const overUnderCell = row.cells[row.cells.length - 1];
overUnderCell.removeChild(overUnderCell.querySelector('.spinner'));
overUnderCell.innerHTML = '';
const overUnderDiv = document.createElement('div');
overUnderDiv.className = 'over-under-wrapper hidden';
if (data.over_unders[index]['Probability'][0] > 0.6){
overUnderDiv.classList.add("highlight");
}
else {
overUnderDiv.style.opacity = "0.5";
}
const textDiv = document.createElement('div');
textDiv.className = 'over-under-text';
textDiv.textContent = data.over_unders[index]['Over/Under'];
if (textDiv.textContent === 'Over') {
overUnderDiv.className += ' over';
} else if (textDiv.textContent === 'Under') {
overUnderDiv.className += ' under';
} else {
overUnderDiv.className += ' na';
}
overUnderDiv.appendChild(textDiv);
const overEmojiDiv = document.createElement('div');
overEmojiDiv.className = 'emoji';
overUnderDiv.dataset.proba = Math.floor(data.over_unders[index]['Probability'][0] * 100).toFixed(0);
if (data.over_unders[index]['Over/Under'][0] === data.over_unders[index]['Result']) {
overEmojiDiv.textContent = 'βœ…';
}
else if (data.over_unders[index]['Result'] === 'Push') {
overEmojiDiv.textContent = 'πŸ”΅';
}
else {
overEmojiDiv.textContent = '❌';
}
if (data.over_unders[index]['Result'] === 'N/A') {
overEmojiDiv.textContent = `(${overUnderDiv.dataset.proba}%)`;
}
overUnderDiv.appendChild(overEmojiDiv);
setTimeout(() => {
overUnderDiv.classList.remove('hidden');
}, 10);
overUnderCell.appendChild(overUnderDiv);
showProbabilityOnHover(wrapperDiv);
showProbabilityOnHover(overUnderDiv);
});
}
});
}
//Hover listener
function showProbabilityOnHover(div) {
let previousValue;
let divText = div.children[1];
let eventProcessed = false;
function handleEnter() {
if (eventProcessed) return; // Skip if an event has already been processed
eventProcessed = true;
if (divText.textContent !== `(${div.dataset.proba}%)`) {
divText.style.opacity = 0;
setTimeout(() => {
previousValue = divText.textContent;
divText.textContent = `(${div.dataset.proba}%)`;
divText.style.opacity = 1;
}, 300);
setTimeout(() => {
divText.style.opacity = 0;
setTimeout(() => {
divText.textContent = previousValue;
divText.style.opacity = 1;
eventProcessed = false; // Reset the flag
}, 300);
}, 1000);
}
}
// For desktop
div.addEventListener('mouseenter', handleEnter);
// For mobile
div.addEventListener('touchstart', handleEnter);
}
// Populate dropdown
let selectedWeek;
async function populateDropdown() {
const weekSelector = document.getElementById('weekSelector');
weekSelector.innerHTML = "";
const response = await fetch('/get_weeks');
const data = await response.json();
data.forEach((week, index) => {
const option = document.createElement('option');
option.value = week;
option.text = `Week ${week}`;
weekSelector.appendChild(option);
if (index === 0) {
selectedWeek = week;
}
});
}
// Get new games when new week selected
document.getElementById('weekSelector').addEventListener('change', function(event) {
selectedWeek = event.target.value;
getNew();
});
// Initial load
function loadThings() {
populateDropdown()
.then(() => fetchGames(selectedWeek))
.then(() => submitData())
.catch(error => console.error(error));
}
// Get new
async function getNew() {
const table = document.getElementById('gameTable');
table.style.opacity = "0.5";
try {
await fetchGames(selectedWeek);
await submitData();
table.style.opacity = "1";
} catch (error) {
console.error(error);
}
}
// Submit on click, enter, and pageload
loadThings();
document.getElementById('submitButton').addEventListener('click', submitData);
document.addEventListener('keydown', function(event) {
if (event.keyCode === 13) {
submitData();
}
});
</script>
</body>
</html>