| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Football Route Visualizer</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .field { |
| background-color: #3a5f0b; |
| position: relative; |
| overflow: hidden; |
| } |
| .yard-line { |
| position: absolute; |
| width: 2px; |
| background-color: white; |
| height: 100%; |
| top: 0; |
| } |
| .yard-number { |
| position: absolute; |
| color: white; |
| font-weight: bold; |
| font-size: 12px; |
| transform: translateX(-50%); |
| } |
| .hash-mark { |
| position: absolute; |
| width: 10px; |
| height: 2px; |
| background-color: white; |
| } |
| .player { |
| transition: all 0.5s ease; |
| } |
| .route-path { |
| stroke-dasharray: 1000; |
| stroke-dashoffset: 1000; |
| animation: dash 2s linear forwards; |
| } |
| @keyframes dash { |
| to { |
| stroke-dashoffset: 0; |
| } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100"> |
| <div class="container mx-auto px-4 py-8"> |
| <header class="text-center mb-8"> |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">Football Route Visualizer</h1> |
| <p class="text-lg text-gray-600">Interactive tool to understand receiver route concepts</p> |
| </header> |
|
|
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
| |
| <div class="bg-white rounded-lg shadow-md p-6"> |
| <h2 class="text-2xl font-semibold mb-4 text-gray-800">Route Concepts</h2> |
| |
| <div class="space-y-4"> |
| <div class="grid grid-cols-2 gap-3"> |
| <button onclick="showRoute('go')" class="route-btn bg-blue-100 hover:bg-blue-200 text-blue-800 py-2 px-4 rounded-lg transition"> |
| <i class="fas fa-arrow-up mr-2"></i> Go Route |
| </button> |
| <button onclick="showRoute('slant')" class="route-btn bg-green-100 hover:bg-green-200 text-green-800 py-2 px-4 rounded-lg transition"> |
| <i class="fas fa-arrow-up-right mr-2"></i> Slant |
| </button> |
| <button onclick="showRoute('curl')" class="route-btn bg-purple-100 hover:bg-purple-200 text-purple-800 py-2 px-4 rounded-lg transition"> |
| <i class="fas fa-undo mr-2"></i> Curl |
| </button> |
| <button onclick="showRoute('out')" class="route-btn bg-yellow-100 hover:bg-yellow-200 text-yellow-800 py-2 px-4 rounded-lg transition"> |
| <i class="fas fa-arrow-right mr-2"></i> Out Route |
| </button> |
| <button onclick="showRoute('post')" class="route-btn bg-red-100 hover:bg-red-200 text-red-800 py-2 px-4 rounded-lg transition"> |
| <i class="fas fa-long-arrow-alt-up mr-2"></i> Post |
| </button> |
| <button onclick="showRoute('corner')" class="route-btn bg-indigo-100 hover:bg-indigo-200 text-indigo-800 py-2 px-4 rounded-lg transition"> |
| <i class="fas fa-arrow-up-right mr-2"></i> Corner |
| </button> |
| </div> |
| |
| <div class="pt-4 border-t border-gray-200"> |
| <h3 class="font-medium text-gray-700 mb-2">Route Combinations</h3> |
| <div class="grid grid-cols-1 gap-3"> |
| <button onclick="showRoute('smash')" class="route-btn bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-4 rounded-lg transition"> |
| Smash Concept |
| </button> |
| <button onclick="showRoute('levels')" class="route-btn bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-4 rounded-lg transition"> |
| Levels Concept |
| </button> |
| <button onclick="showRoute('mesh')" class="route-btn bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-4 rounded-lg transition"> |
| Mesh Concept |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="lg:col-span-2"> |
| <div class="bg-white rounded-lg shadow-md p-4"> |
| <div class="flex justify-between items-center mb-4"> |
| <h2 class="text-2xl font-semibold text-gray-800">Route Visualization</h2> |
| <div class="flex space-x-2"> |
| <button onclick="resetField()" class="bg-gray-200 hover:bg-gray-300 text-gray-800 py-1 px-3 rounded-lg text-sm transition"> |
| <i class="fas fa-redo mr-1"></i> Reset |
| </button> |
| <button onclick="toggleAnimation()" class="bg-gray-200 hover:bg-gray-300 text-gray-800 py-1 px-3 rounded-lg text-sm transition"> |
| <i class="fas fa-play mr-1"></i> Animate |
| </button> |
| </div> |
| </div> |
| |
| <div class="field rounded-lg relative" style="height: 500px;"> |
| |
| <svg id="route-svg" width="100%" height="100%" style="position: absolute; top: 0; left: 0;"></svg> |
| |
| |
| <div id="qb" class="player absolute w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-white font-bold" style="bottom: 50px; left: 50%; transform: translateX(-50%);">QB</div> |
| |
| <div id="wr1" class="player absolute w-8 h-8 bg-red-500 rounded-full flex items-center justify-center text-white font-bold" style="bottom: 150px; left: 20%;">WR1</div> |
| <div id="wr2" class="player absolute w-8 h-8 bg-red-500 rounded-full flex items-center justify-center text-white font-bold" style="bottom: 150px; left: 40%;">WR2</div> |
| <div id="wr3" class="player absolute w-8 h-8 bg-red-500 rounded-full flex items-center justify-center text-white font-bold" style="bottom: 150px; left: 60%;">WR3</div> |
| <div id="wr4" class="player absolute w-8 h-8 bg-red-500 rounded-full flex items-center justify-center text-white font-bold" style="bottom: 150px; left: 80%;">WR4</div> |
| |
| |
| <div id="cb1" class="player absolute w-8 h-8 bg-gray-700 rounded-full flex items-center justify-center text-white font-bold" style="bottom: 200px; left: 20%; opacity: 0.7;">CB</div> |
| <div id="cb2" class="player absolute w-8 h-8 bg-gray-700 rounded-full flex items-center justify-center text-white font-bold" style="bottom: 200px; left: 40%; opacity: 0.7;">CB</div> |
| <div id="cb3" class="player absolute w-8 h-8 bg-gray-700 rounded-full flex items-center justify-center text-white font-bold" style="bottom: 200px; left: 60%; opacity: 0.7;">CB</div> |
| <div id="cb4" class="player absolute w-8 h-8 bg-gray-700 rounded-full flex items-center justify-center text-white font-bold" style="bottom: 200px; left: 80%; opacity: 0.7;">CB</div> |
| </div> |
| |
| <div id="route-description" class="mt-4 p-4 bg-gray-50 rounded-lg"> |
| <h3 class="font-semibold text-lg text-gray-800 mb-2">Route Information</h3> |
| <p class="text-gray-600">Select a route from the panel to see visualization and details.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| drawField(); |
| }); |
| |
| function drawField() { |
| const field = document.querySelector('.field'); |
| const width = field.offsetWidth; |
| const height = field.offsetHeight; |
| |
| |
| for (let i = 0; i <= 100; i += 5) { |
| const yardLine = document.createElement('div'); |
| yardLine.className = 'yard-line'; |
| yardLine.style.left = `${(i / 100) * 100}%`; |
| |
| |
| if (i % 10 === 0 && i > 0 && i < 100) { |
| const yardNumber = document.createElement('div'); |
| yardNumber.className = 'yard-number'; |
| yardNumber.style.left = `${(i / 100) * 100}%`; |
| yardNumber.style.top = '10px'; |
| yardNumber.textContent = i; |
| field.appendChild(yardNumber); |
| |
| const yardNumberBottom = document.createElement('div'); |
| yardNumberBottom.className = 'yard-number'; |
| yardNumberBottom.style.left = `${(i / 100) * 100}%`; |
| yardNumberBottom.style.bottom = '10px'; |
| yardNumberBottom.textContent = i; |
| field.appendChild(yardNumberBottom); |
| } |
| |
| field.appendChild(yardLine); |
| } |
| |
| |
| for (let i = 10; i < 100; i += 5) { |
| const hashTop = document.createElement('div'); |
| hashTop.className = 'hash-mark'; |
| hashTop.style.left = `${(i / 100) * 100}%`; |
| hashTop.style.top = '30%'; |
| field.appendChild(hashTop); |
| |
| const hashBottom = document.createElement('div'); |
| hashBottom.className = 'hash-mark'; |
| hashBottom.style.left = `${(i / 100) * 100}%`; |
| hashBottom.style.bottom = '30%'; |
| field.appendChild(hashBottom); |
| } |
| } |
| |
| |
| function showRoute(routeType) { |
| resetField(); |
| const svg = document.getElementById('route-svg'); |
| svg.innerHTML = ''; |
| |
| |
| document.querySelectorAll('.route-btn').forEach(btn => { |
| btn.classList.remove('ring-2', 'ring-blue-500'); |
| }); |
| event.target.classList.add('ring-2', 'ring-blue-500'); |
| |
| |
| const descriptions = { |
| 'go': { |
| text: 'The Go Route (or Fly Route) is a straight sprint downfield. The receiver aims to get behind the defender for a deep pass. Key points: Explode off the line, maintain speed, track the ball over your shoulder.', |
| color: 'blue' |
| }, |
| 'slant': { |
| text: 'The Slant Route is a quick inside cut at about 5 yards. The receiver takes 3 hard steps forward then cuts at a 45-degree angle. Key points: Sharp cut, protect the ball in traffic, find the open window.', |
| color: 'green' |
| }, |
| 'curl': { |
| text: 'The Curl Route is a deep out-and-back move. The receiver runs 10-12 yards downfield then turns back toward the QB. Key points: Sell the go route first, plant hard on the back foot, create separation.', |
| color: 'purple' |
| }, |
| 'out': { |
| text: 'The Out Route is a 90-degree cut toward the sideline at 5-10 yards. The receiver runs vertically then makes a sharp outside cut. Key points: Depth depends on QB arm strength, keep feet in bounds, accelerate out of break.', |
| color: 'yellow' |
| }, |
| 'post': { |
| text: 'The Post Route is a deep diagonal cut toward the goalposts. The receiver runs 10-15 yards then cuts at a 45-degree angle toward the middle. Key points: Sell the go route first, maintain speed through cut, track the deep ball.', |
| color: 'red' |
| }, |
| 'corner': { |
| text: 'The Corner Route is a deep outside cut toward the corner of the end zone. The receiver runs 10-15 yards then cuts at a 45-degree angle toward the sideline. Key points: Sell the post route first, create separation with speed change.', |
| color: 'indigo' |
| }, |
| 'smash': { |
| text: 'The Smash Concept combines a short corner route (5-7 yards) with a deeper curl (12-15 yards). This creates a high-low read for the QB against zone coverage. The corner route occupies the flat defender while the curl works behind.', |
| color: 'gray' |
| }, |
| 'levels': { |
| text: 'The Levels Concept uses multiple receivers running crossing routes at different depths (typically 5 and 10 yards). This stretches zone coverage vertically and creates natural picks against man coverage.', |
| color: 'gray' |
| }, |
| 'mesh': { |
| text: 'The Mesh Concept has two receivers crossing close to the line of scrimmage (3-5 yards depth) while other receivers clear out. The crossing routes create natural picks and confusion in man coverage.', |
| color: 'gray' |
| } |
| }; |
| |
| |
| const descDiv = document.getElementById('route-description'); |
| descDiv.innerHTML = ` |
| <h3 class="font-semibold text-lg text-gray-800 mb-2">${routeType.charAt(0).toUpperCase() + routeType.slice(1)} Route</h3> |
| <p class="text-gray-600">${descriptions[routeType].text}</p> |
| `; |
| |
| |
| const routeColor = descriptions[routeType].color; |
| |
| switch(routeType) { |
| case 'go': |
| drawPath(svg, 20, 150, 20, 50, routeColor); |
| animatePlayer('wr1', 20, 150, 20, 50); |
| break; |
| case 'slant': |
| drawPath(svg, 20, 150, 40, 100, routeColor); |
| animatePlayer('wr1', 20, 150, 40, 100); |
| break; |
| case 'curl': |
| drawPath(svg, 40, 150, 40, 100, routeColor); |
| drawPath(svg, 40, 100, 30, 100, routeColor); |
| animatePlayer('wr2', 40, 150, 40, 100, () => { |
| animatePlayer('wr2', 40, 100, 30, 100); |
| }); |
| break; |
| case 'out': |
| drawPath(svg, 60, 150, 60, 100, routeColor); |
| drawPath(svg, 60, 100, 80, 100, routeColor); |
| animatePlayer('wr3', 60, 150, 60, 100, () => { |
| animatePlayer('wr3', 60, 100, 80, 100); |
| }); |
| break; |
| case 'post': |
| drawPath(svg, 80, 150, 80, 80, routeColor); |
| drawPath(svg, 80, 80, 60, 50, routeColor); |
| animatePlayer('wr4', 80, 150, 80, 80, () => { |
| animatePlayer('wr4', 80, 80, 60, 50); |
| }); |
| break; |
| case 'corner': |
| drawPath(svg, 20, 150, 20, 80, routeColor); |
| drawPath(svg, 20, 80, 40, 50, routeColor); |
| animatePlayer('wr1', 20, 150, 20, 80, () => { |
| animatePlayer('wr1', 20, 80, 40, 50); |
| }); |
| break; |
| case 'smash': |
| |
| drawPath(svg, 20, 150, 20, 120, routeColor); |
| drawPath(svg, 20, 120, 40, 100, routeColor); |
| |
| drawPath(svg, 40, 150, 40, 100, routeColor); |
| drawPath(svg, 40, 100, 30, 100, routeColor); |
| |
| animatePlayer('wr1', 20, 150, 20, 120, () => { |
| animatePlayer('wr1', 20, 120, 40, 100); |
| }); |
| animatePlayer('wr2', 40, 150, 40, 100, () => { |
| animatePlayer('wr2', 40, 100, 30, 100); |
| }); |
| break; |
| case 'levels': |
| |
| drawPath(svg, 20, 150, 80, 130, routeColor); |
| |
| drawPath(svg, 40, 150, 60, 100, routeColor); |
| |
| animatePlayer('wr1', 20, 150, 80, 130); |
| animatePlayer('wr2', 40, 150, 60, 100); |
| break; |
| case 'mesh': |
| |
| drawPath(svg, 20, 150, 80, 130, routeColor); |
| |
| drawPath(svg, 80, 150, 20, 130, routeColor); |
| |
| animatePlayer('wr1', 20, 150, 80, 130); |
| animatePlayer('wr2', 80, 150, 20, 130); |
| break; |
| } |
| } |
| |
| function drawPath(svg, x1, y1, x2, y2, color) { |
| const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); |
| path.setAttribute('d', `M ${x1}% ${y1} L ${x2}% ${y2}`); |
| path.setAttribute('stroke', color); |
| path.setAttribute('stroke-width', '3'); |
| path.setAttribute('fill', 'none'); |
| path.classList.add('route-path'); |
| svg.appendChild(path); |
| } |
| |
| function animatePlayer(playerId, startX, startY, endX, endY, callback) { |
| const player = document.getElementById(playerId); |
| player.style.left = `${startX}%`; |
| player.style.bottom = `${startY}px`; |
| |
| setTimeout(() => { |
| player.style.left = `${endX}%`; |
| player.style.bottom = `${endY}px`; |
| |
| if (callback) { |
| setTimeout(callback, 1000); |
| } |
| }, 100); |
| } |
| |
| function resetField() { |
| const svg = document.getElementById('route-svg'); |
| svg.innerHTML = ''; |
| |
| |
| document.getElementById('wr1').style.left = '20%'; |
| document.getElementById('wr1').style.bottom = '150px'; |
| |
| document.getElementById('wr2').style.left = '40%'; |
| document.getElementById('wr2').style.bottom = '150px'; |
| |
| document.getElementById('wr3').style.left = '60%'; |
| document.getElementById('wr3').style.bottom = '150px'; |
| |
| document.getElementById('wr4').style.left = '80%'; |
| document.getElementById('wr4').style.bottom = '150px'; |
| |
| |
| document.getElementById('route-description').innerHTML = ` |
| <h3 class="font-semibold text-lg text-gray-800 mb-2">Route Information</h3> |
| <p class="text-gray-600">Select a route from the panel to see visualization and details.</p> |
| `; |
| } |
| |
| function toggleAnimation() { |
| const paths = document.querySelectorAll('.route-path'); |
| paths.forEach(path => { |
| if (path.style.animationPlayState === 'paused') { |
| path.style.animationPlayState = 'running'; |
| } else { |
| path.style.animationPlayState = 'paused'; |
| } |
| }); |
| } |
| </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=GUZBOMB/route-visual-v1-sample-fix" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |