Trainingsplan

/* Alle Styles sind strikt auf .tp-download-card-wrap und Kinder beschränkt - kein Einfluss auf die restliche Seite */ .tp-download-card-wrap { display: block; } .tp-download-card-wrap *, .tp-download-card-wrap *::before, .tp-download-card-wrap *::after { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Barlow', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; } .tp-download-card-wrap .tp-card { --tp-navy: rgb(21,33,69); --tp-gold: rgb(246,195,70); --tp-border: #e8ecf2; --tp-text-body: #555; --tp-text-muted: #888; color: var(--tp-navy); line-height: 1.6; max-width: 820px; margin: 0 auto; background: #ffffff; border: 1px solid var(--tp-border); padding: 44px 48px; position: relative; box-shadow: 0 4px 20px rgba(21,33,69,0.05); transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s; display: block; } .tp-download-card-wrap .tp-card:hover { border-color: var(--tp-gold); transform: translateY(-4px); box-shadow: 0 12px 36px rgba(21,33,69,0.10); } .tp-download-card-wrap .tp-card::before, .tp-download-card-wrap .tp-card::after { content: ''; position: absolute; width: 22px; height: 22px; pointer-events: none; } .tp-download-card-wrap .tp-card::before { top: -1px; left: -1px; border-top: 2px solid var(--tp-gold); border-left: 2px solid var(--tp-gold); } .tp-download-card-wrap .tp-card::after { bottom: -1px; right: -1px; border-bottom: 2px solid var(--tp-gold); border-right: 2px solid var(--tp-gold); } .tp-download-card-wrap .tp-content { display: grid; grid-template-columns: auto 1fr auto; gap: 32px; align-items: center; } .tp-download-card-wrap .tp-icon { width: 88px; height: 108px; background: var(--tp-gold); position: relative; display: flex; align-items: flex-end; justify-content: center; padding-bottom: 14px; font-weight: 900; color: var(--tp-navy); font-size: 20px; letter-spacing: 1.5px; } .tp-download-card-wrap .tp-icon::before { content: ''; position: absolute; top: 0; right: 0; width: 22px; height: 22px; background: #ffffff; clip-path: polygon(0 0, 100% 100%, 100% 0); } .tp-download-card-wrap .tp-text { display: block; } .tp-download-card-wrap .tp-label { font-size: 12px; letter-spacing: 3px; text-transform: uppercase; color: var(--tp-text-muted); font-weight: 700; margin-bottom: 8px; display: block; } .tp-download-card-wrap .tp-title { font-size: 28px; font-weight: 900; color: var(--tp-navy); line-height: 1.2; letter-spacing: 0.3px; margin-bottom: 8px; display: block; } .tp-download-card-wrap .tp-subtitle { font-size: 15px; color: var(--tp-text-body); line-height: 1.55; display: block; } .tp-download-card-wrap .tp-btn { background: var(--tp-navy); color: var(--tp-gold); text-decoration: none; padding: 16px 30px; font-weight: 900; font-size: 14px; letter-spacing: 2px; text-transform: uppercase; display: inline-flex; align-items: center; gap: 10px; border: 2px solid var(--tp-navy); transition: gap 0.3s, transform 0.3s, background 0.3s; white-space: nowrap; cursor: pointer; } .tp-download-card-wrap .tp-btn:hover { gap: 14px; transform: translateY(-3px); background: #1a2a5a; color: var(--tp-gold); } .tp-download-card-wrap .tp-arrow { font-size: 18px; transition: transform 0.3s; display: inline-block; } .tp-download-card-wrap .tp-btn:hover .tp-arrow { transform: translateY(3px); } @media (max-width: 720px) { .tp-download-card-wrap .tp-card { padding: 36px 28px; } .tp-download-card-wrap .tp-content { grid-template-columns: 1fr; text-align: center; gap: 22px; } .tp-download-card-wrap .tp-icon { margin: 0 auto; } .tp-download-card-wrap .tp-title { font-size: 22px; } .tp-download-card-wrap .tp-btn { justify-content: center; } }
PDF
Camp 2026

Aktueller Trainingsplan

Vollständige Übersicht aller Trainingseinheiten und Zeiten.

Download
Typischer Tagesablauf body { font-family: Arial, sans-serif; background-color: #f5f5f5; } h2 { margin-left: 40px; } table { border-collapse: collapse; width: 90%; margin: 20px auto; background-color: #f9f9f9; } th, td { border: 2px solid #ccc; padding: 20px; text-align: left; font-size: 20px; color: #777; } thead th { background-color: rgb(246, 195, 70); color: black; font-size: 22px; }

Typischer Tagesablauf

Uhrzeit Aktivität Ort
08:00-09:00 Aufwärmen / Trockentraining Kurbads, Rasen
09:30-11:30 Eis-Training (Skating, Technik) Kurbads Eishalle
11:30-13:00 Mittagspause Kurbads, Café
13:00-14:30 Theorie / Videoanalyse Seminarraum
15:00-17:00 Eis-Training (Spiel, Taktik) Kurbads Eishalle
.hk-game-wrap { display: block; width: 100%; } .hk-game-wrap *, .hk-game-wrap *::before, .hk-game-wrap *::after { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Barlow', -apple-system, BlinkMacSystemFont, sans-serif; } .hk-game-wrap .hk-container { max-width: 900px; margin: 0 auto; padding: 24px 20px; user-select: none; } /* Score bar */ .hk-game-wrap .hk-scorebar { display: flex; align-items: stretch; justify-content: space-between; background: rgb(21,33,69); color: #fff; border-radius: 8px 8px 0 0; padding: 14px 22px; box-shadow: 0 4px 14px rgba(21,33,69,0.2); } .hk-game-wrap .hk-score-block { display: flex; flex-direction: column; align-items: center; gap: 4px; } .hk-game-wrap .hk-score-label { font-size: 10px; letter-spacing: 2.5px; text-transform: uppercase; font-weight: 700; opacity: 0.7; } .hk-game-wrap .hk-score-block.you .hk-score-label { color: rgb(246,195,70); opacity: 1; } .hk-game-wrap .hk-score-block.cpu .hk-score-label { color: #ff6b6b; opacity: 1; } .hk-game-wrap .hk-score-num { font-family: 'Barlow Condensed', sans-serif; font-size: 40px; font-weight: 900; line-height: 1; } .hk-game-wrap .hk-score-divider { color: rgb(246,195,70); font-family: 'Barlow Condensed', sans-serif; font-size: 36px; font-weight: 900; align-self: center; opacity: 0.6; } .hk-game-wrap .hk-score-stat { display: flex; flex-direction: column; justify-content: center; text-align: center; min-width: 70px; } .hk-game-wrap .hk-stat-label { font-size: 9px; letter-spacing: 1.5px; text-transform: uppercase; opacity: 0.6; margin-bottom: 2px; } .hk-game-wrap .hk-stat-val { font-family: 'Barlow Condensed', sans-serif; font-size: 22px; font-weight: 800; color: rgb(246,195,70); } /* Rink */ .hk-game-wrap .hk-rink { position: relative; width: 100%; aspect-ratio: 2 / 1; background: linear-gradient(180deg, #ffffff 0%, #e8f0ff 100%); border: 4px solid rgb(21,33,69); border-top: none; border-radius: 0 0 80px 80px / 0 0 60px 60px; overflow: hidden; cursor: none; box-shadow: 0 12px 40px rgba(21,33,69,0.18), inset 0 0 60px rgba(180,210,255,0.4); touch-action: none; } .hk-game-wrap .hk-rink::before { content: ''; position: absolute; inset: 0; background: repeating-linear-gradient(90deg, transparent, transparent 8px, rgba(180,210,255,0.15) 8px, rgba(180,210,255,0.15) 9px); pointer-events: none; } /* lines */ .hk-game-wrap .hk-line-center { position: absolute; top: 0; bottom: 0; left: 50%; width: 4px; background: #d93636; transform: translateX(-50%); } .hk-game-wrap .hk-line-blue { position: absolute; top: 0; bottom: 0; width: 3px; background: #2962d6; } .hk-game-wrap .hk-line-blue.l { left: 33%; } .hk-game-wrap .hk-line-blue.r { left: 67%; } /* center circle */ .hk-game-wrap .hk-circle-center { position: absolute; top: 50%; left: 50%; width: 22%; aspect-ratio: 1; border: 2px solid #2962d6; border-radius: 50%; transform: translate(-50%, -50%); } .hk-game-wrap .hk-circle-center::after { content: ''; position: absolute; top: 50%; left: 50%; width: 12px; height: 12px; background: #2962d6; border-radius: 50%; transform: translate(-50%, -50%); } /* faceoff */ .hk-game-wrap .hk-faceoff { position: absolute; width: 13%; aspect-ratio: 1; border: 2px solid #d93636; border-radius: 50%; } .hk-game-wrap .hk-faceoff::after { content: ''; position: absolute; top: 50%; left: 50%; width: 8px; height: 8px; background: #d93636; border-radius: 50%; transform: translate(-50%, -50%); } .hk-game-wrap .hk-faceoff.tl { top: 18%; left: 12%; } .hk-game-wrap .hk-faceoff.bl { bottom: 18%; left: 12%; } .hk-game-wrap .hk-faceoff.tr { top: 18%; right: 12%; } .hk-game-wrap .hk-faceoff.br { bottom: 18%; right: 12%; } /* Goals */ .hk-game-wrap .hk-goal { position: absolute; top: 50%; width: 14px; height: 28%; border: 3px solid #d93636; background: rgba(217,54,54,0.12); transform: translateY(-50%); } .hk-game-wrap .hk-goal.l { left: 6px; border-radius: 4px 0 0 4px; } .hk-game-wrap .hk-goal.r { right: 6px; border-radius: 0 4px 4px 0; } /* crease */ .hk-game-wrap .hk-crease { position: absolute; top: 50%; width: 11%; aspect-ratio: 1; border: 2px solid #2962d6; background: rgba(41,98,214,0.1); transform: translateY(-50%); } .hk-game-wrap .hk-crease.l { left: 20px; border-radius: 0 100% 100% 0 / 0 50% 50% 0; border-left: none; } .hk-game-wrap .hk-crease.r { right: 20px; border-radius: 100% 0 0 100% / 50% 0 0 50%; border-right: none; } /* PUCK */ .hk-game-wrap .hk-puck { position: absolute; width: 26px; height: 26px; background: radial-gradient(circle at 35% 30%, #555 0%, #1a1a1a 50%, #000 100%); border-radius: 50%; box-shadow: 0 6px 14px rgba(0,0,0,0.5), inset 0 -3px 5px rgba(0,0,0,0.6); z-index: 5; transform: translate(-50%, -50%); transition: opacity 0.3s; } .hk-game-wrap .hk-puck::before { content: ''; position: absolute; top: 18%; left: 22%; width: 35%; height: 25%; background: rgba(255,255,255,0.25); border-radius: 50%; filter: blur(2px); } /* STICK / paddle (player controlled) */ .hk-game-wrap .hk-stick { position: absolute; width: 18px; height: 130px; z-index: 10; transform: translate(-50%, -50%); pointer-events: none; filter: drop-shadow(0 4px 8px rgba(0,0,0,0.35)); } /* shaft - longer wood + grip tape */ .hk-game-wrap .hk-stick-shaft { position: absolute; left: 50%; bottom: 4px; width: 7px; height: 102px; background: linear-gradient(to right, #f5d088 0%, #d9a35a 35%, #a87a3c 65%, #6e4d22 100%); border-radius: 4px; transform: translateX(-50%); box-shadow: inset 1px 0 0 rgba(255,255,255,0.4), inset -1px 0 0 rgba(0,0,0,0.3); } /* grip tape on shaft (right end) */ .hk-game-wrap .hk-stick-shaft::before { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 26px; background: repeating-linear-gradient( 45deg, #1a1a1a, #1a1a1a 3px, #2c2c2c 3px, #2c2c2c 6px); border-radius: 0 0 4px 4px; } /* shaft polish stripe */ .hk-game-wrap .hk-stick-shaft::after { content: ''; position: absolute; left: 1px; top: 4px; bottom: 30px; width: 1.5px; background: rgba(255,255,255,0.55); border-radius: 2px; } /* blade — proper hockey blade curve */ .hk-game-wrap .hk-stick-blade { position: absolute; top: 0; left: 50%; width: 18px; height: 36px; background: linear-gradient(to right, #3a3a3a 0%, #1a1a1a 50%, #000 100%); border-radius: 6px 8px 2px 2px / 8px 10px 2px 2px; transform: translateX(-50%) skewX(-3deg); box-shadow: inset 1px 0 0 rgba(255,255,255,0.15), inset -2px 0 4px rgba(0,0,0,0.6); } /* white blade tape stripe */ .hk-game-wrap .hk-stick-blade::before { content: ''; position: absolute; left: 3px; top: 4px; bottom: 4px; width: 3px; background: linear-gradient(to right, #ffffff 0%, #d8d8d8 100%); border-radius: 2px; box-shadow: 1px 0 0 rgba(0,0,0,0.3); } /* blade heel highlight */ .hk-game-wrap .hk-stick-blade::after { content: ''; position: absolute; right: 2px; top: 4px; width: 2px; height: 8px; background: rgba(255,255,255,0.2); border-radius: 2px; filter: blur(0.5px); } /* CPU stick (mirrored - blade on right) */ .hk-game-wrap .hk-stick-cpu { width: 18px; height: 130px; } .hk-game-wrap .hk-stick-shaft-cpu { position: absolute; left: 50%; top: 4px; width: 7px; height: 102px; background: linear-gradient(to right, #ff7777 0%, #d93636 35%, #a82828 65%, #6e1818 100%); border-radius: 4px; transform: translateX(-50%); box-shadow: inset 1px 0 0 rgba(255,255,255,0.4), inset -1px 0 0 rgba(0,0,0,0.3); } .hk-game-wrap .hk-stick-shaft-cpu::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 26px; background: repeating-linear-gradient( -45deg, #1a1a1a, #1a1a1a 3px, #2c2c2c 3px, #2c2c2c 6px); border-radius: 4px 4px 0 0; } .hk-game-wrap .hk-stick-shaft-cpu::after { content: ''; position: absolute; left: 1px; bottom: 4px; top: 30px; width: 1.5px; background: rgba(255,255,255,0.55); border-radius: 2px; } .hk-game-wrap .hk-stick-blade-cpu { position: absolute; bottom: 0; left: 50%; width: 18px; height: 36px; background: linear-gradient(to right, #3a3a3a 0%, #1a1a1a 50%, #000 100%); border-radius: 2px 2px 8px 6px / 2px 2px 10px 8px; transform: translateX(-50%) skewX(3deg); box-shadow: inset 1px 0 0 rgba(255,255,255,0.15), inset -2px 0 4px rgba(0,0,0,0.6); } .hk-game-wrap .hk-stick-blade-cpu::before { content: ''; position: absolute; left: 3px; top: 4px; bottom: 4px; width: 3px; background: linear-gradient(to right, #ffffff 0%, #d8d8d8 100%); border-radius: 2px; box-shadow: 1px 0 0 rgba(0,0,0,0.3); } .hk-game-wrap .hk-stick-blade-cpu::after { content: ''; position: absolute; right: 2px; bottom: 4px; width: 2px; height: 8px; background: rgba(255,255,255,0.2); border-radius: 2px; filter: blur(0.5px); } /* goalie (right side - opponent) - LEGACY (unused, kept harmless) */ .hk-game-wrap .hk-goalie { position: absolute; right: 30px; top: 50%; width: 36px; height: 60px; transform: translateY(-50%); z-index: 8; transition: top 0.2s ease-out; } .hk-game-wrap .hk-goalie-body { position: absolute; inset: 0; background: linear-gradient(180deg, #d93636 0%, #a82828 100%); border-radius: 8px; border: 2px solid #fff; box-shadow: 0 4px 10px rgba(0,0,0,0.3); } .hk-game-wrap .hk-goalie-helmet { position: absolute; top: -10px; left: 50%; width: 22px; height: 22px; background: #fff; border-radius: 50%; border: 2px solid #d93636; transform: translateX(-50%); } /* Goal flash overlay */ .hk-game-wrap .hk-flash { position: absolute; inset: 0; pointer-events: none; opacity: 0; z-index: 20; } .hk-game-wrap .hk-flash.goal-cpu { background: radial-gradient(circle at center, rgba(217,54,54,0.6), transparent 70%); animation: hk-flash-anim 0.6s ease-out; } .hk-game-wrap .hk-flash.goal-you { background: radial-gradient(circle at center, rgba(246,195,70,0.6), transparent 70%); animation: hk-flash-anim 0.6s ease-out; } @keyframes hk-flash-anim { 0% { opacity: 0; } 30% { opacity: 1; } 100% { opacity: 0; } } .hk-game-wrap .hk-confetti { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 22; } .hk-game-wrap .hk-confetti span { position: absolute; width: 10px; height: 14px; top: -20px; border-radius: 2px; animation: hk-confetti-fall 1.8s linear forwards; opacity: 0.95; } @keyframes hk-confetti-fall { 0% { transform: translateY(0) rotate(0deg); opacity: 1; } 100% { transform: translateY(120%) rotate(720deg); opacity: 0; } } /* Goal banner */ .hk-game-wrap .hk-banner { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0); background: rgb(21,33,69); color: rgb(246,195,70); font-family: 'Barlow Condensed', sans-serif; font-size: 56px; font-weight: 900; letter-spacing: 6px; padding: 12px 40px; border: 3px solid rgb(246,195,70); z-index: 25; pointer-events: none; opacity: 0; } .hk-game-wrap .hk-banner.show { animation: hk-banner-anim 1.3s ease-out; } @keyframes hk-banner-anim { 0% { transform: translate(-50%, -50%) scale(0); opacity: 0; } 25% { transform: translate(-50%, -50%) scale(1.15); opacity: 1; } 40% { transform: translate(-50%, -50%) scale(1); opacity: 1; } 80% { opacity: 1; } 100% { transform: translate(-50%, -50%) scale(1); opacity: 0; } } /* Pause overlay */ .hk-game-wrap .hk-pause-overlay { position: absolute; inset: 0; background: rgba(21,33,69,0.78); display: none; align-items: center; justify-content: center; font-family: 'Barlow Condensed', sans-serif; font-size: 90px; font-weight: 900; letter-spacing: 12px; color: rgb(246,195,70); z-index: 30; pointer-events: none; text-shadow: 0 4px 20px rgba(0,0,0,0.5); } .hk-game-wrap .hk-pause-overlay.show { display: flex; } /* Controls */ .hk-game-wrap .hk-controls { margin-top: 16px; display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; } .hk-game-wrap .hk-btn { background: rgb(21,33,69); color: rgb(246,195,70); border: none; padding: 11px 24px; font-family: 'Barlow Condensed', sans-serif; font-weight: 800; font-size: 13px; letter-spacing: 2px; text-transform: uppercase; cursor: pointer; transition: transform 0.2s, background 0.2s; } .hk-game-wrap .hk-btn:hover { transform: translateY(-2px); background: #1a2a5a; } .hk-game-wrap .hk-btn.gold { background: rgb(246,195,70); color: rgb(21,33,69); } .hk-game-wrap .hk-btn.gold:hover { background: #f0b840; } .hk-game-wrap .hk-btn.active { background: rgb(246,195,70); color: rgb(21,33,69); } .hk-game-wrap .hk-hint { text-align: center; margin-top: 14px; font-size: 12px; color: #888; letter-spacing: 1.5px; text-transform: uppercase; } @media (max-width: 600px) { .hk-game-wrap .hk-scorebar { padding: 10px 14px; gap: 8px; } .hk-game-wrap .hk-score-num { font-size: 30px; } .hk-game-wrap .hk-score-stat { min-width: 50px; } .hk-game-wrap .hk-stat-val { font-size: 18px; } }
Du 0
Saves 0
:
Schüsse 0
Gegner 0
TOR!
PAUSE
Bewege die Maus oder den Finger im Feld – treffe den Puck mit dem Schläger!
(function(){ const rink = document.getElementById('hkRink'); const puck = document.getElementById('hkPuck'); const stick = document.getElementById('hkStick'); const goalie = document.getElementById('hkGoalie'); const flash = document.getElementById('hkFlash'); const banner = document.getElementById('hkBanner'); const scoreYouEl = document.getElementById('hkScoreYou'); const scoreCpuEl = document.getElementById('hkScoreCpu'); const savesEl = document.getElementById('hkSaves'); const shotsEl = document.getElementById('hkShots'); const diffBtns = document.querySelectorAll('.hk-game-wrap .hk-btn[data-diff]'); const resetBtn = document.getElementById('hkReset'); // game state in % coordinates of the rink let puckX = 50, puckY = 50; // % let puckVX = 0, puckVY = 0; // % per frame let stuckCounter = 0; // anti-stuck let stickX = 12, stickY = 50; // % let goalieX = 82, goalieY = 50; // % let scoreYou = 0, scoreCpu = 0; let saves = 0, shots = 0; let running = true; let paused = false; // difficulty config const difficulties = { easy: { puckSpeed: 0.55, goalieSpeed: 0.05, accuracy: 0.55 }, normal: { puckSpeed: 0.85, goalieSpeed: 0.075, accuracy: 0.72 }, hard: { puckSpeed: 1.2, goalieSpeed: 0.11, accuracy: 0.88 } }; let diff = difficulties.normal; diffBtns.forEach(btn => { btn.addEventListener('click', () => { diffBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); diff = difficulties[btn.dataset.diff]; }); }); const pauseBtn = document.getElementById('hkPause'); const pauseOverlay = document.getElementById('hkPauseOverlay'); pauseBtn.addEventListener('click', () => { paused = !paused; pauseBtn.innerHTML = paused ? '▶ Weiter' : '⏸ Pause'; pauseBtn.classList.toggle('active', paused); pauseOverlay.classList.toggle('show', paused); }); resetBtn.addEventListener('click', () => { scoreYou = 0; scoreCpu = 0; saves = 0; shots = 0; updateUI(); resetPuck('center'); }); // input: stick follows mouse / touch function moveStick(e) { const rect = rink.getBoundingClientRect(); const point = e.touches ? e.touches[0] : e; const x = ((point.clientX - rect.left) / rect.width) * 100; const y = ((point.clientY - rect.top) / rect.height) * 100; // stick can move across the whole field stickX = Math.max(6, Math.min(94, x)); stickY = Math.max(8, Math.min(92, y)); } rink.addEventListener('mousemove', moveStick); rink.addEventListener('touchmove', e => { e.preventDefault(); moveStick(e); }, {passive: false}); function resetPuck(side) { puckX = 50; puckY = 50; // shoot toward player by default if (side === 'toYou') { const angle = (Math.random() - 0.5) * 0.4; puckVX = -diff.puckSpeed * Math.cos(angle); puckVY = diff.puckSpeed * Math.sin(angle); } else if (side === 'toCpu') { const angle = (Math.random() - 0.5) * 0.4; puckVX = diff.puckSpeed * Math.cos(angle); puckVY = diff.puckSpeed * Math.sin(angle); } else { // center face-off — random direction const dir = Math.random() < 0.5 ? -1 : 1; puckVX = dir * diff.puckSpeed * 0.9; puckVY = (Math.random() - 0.5) * diff.puckSpeed * 0.6; } } function updateUI() { scoreYouEl.textContent = scoreYou; scoreCpuEl.textContent = scoreCpu; savesEl.textContent = saves; shotsEl.textContent = shots; } function rainConfetti() { const cont = document.getElementById('hkConfetti'); if (!cont) return; cont.innerHTML = ''; const colors = ['rgb(246,195,70)','rgb(21,33,69)','#ff6b6b','#52c4f5','#ffffff','#7bdc8e']; const count = 70; for (let i = 0; i { cont.innerHTML = ''; }, 2400); } function showBanner(text, type) { banner.textContent = text; banner.classList.remove('show'); flash.className = 'hk-flash'; // force reflow to restart animation void banner.offsetWidth; banner.classList.add('show'); flash.classList.add(type === 'cpu' ? 'goal-cpu' : 'goal-you'); setTimeout(() => { flash.className = 'hk-flash'; }, 700); } function tick() { if (!running) return; if (paused) { // when paused: just keep player stick moving with mouse, skip everything else stick.style.left = stickX + '%'; stick.style.top = stickY + '%'; return scheduleNext(); } // move puck puckX += puckVX; puckY += puckVY; // bounce off top/bottom — ensure decent rebound speed const minBounceY = diff.puckSpeed * 0.4; if (puckY = 95) { puckY = 95; puckVY = -Math.max(Math.abs(puckVY), minBounceY); } // anti-stuck: if puck barely moves OR is in a corner with low speed, push it back to play const speedNow = Math.hypot(puckVX, puckVY); const inCorner = (puckX 92) && (puckY 88); if (speedNow < diff.puckSpeed * 0.25 || inCorner && speedNow 25) { // push toward center const angToCenter = Math.atan2(50 - puckY, 50 - puckX); puckVX = Math.cos(angToCenter) * diff.puckSpeed; puckVY = Math.sin(angToCenter) * diff.puckSpeed; stuckCounter = 0; } } else { stuckCounter = 0; } // CPU STICK AI — moves around the right side, chases puck when in CPU half if (puckX > 50) { // chase puck const targetX = puckX + 4 + (Math.random() - 0.5) * (1 - diff.accuracy) * 8; const targetY = puckY + (Math.random() - 0.5) * (1 - diff.accuracy) * 25; goalieX += (targetX - goalieX) * diff.goalieSpeed * 1.6; goalieY += (targetY - goalieY) * diff.goalieSpeed * 1.6; } else { // drift back near right goal area goalieX += (82 - goalieX) * 0.04; goalieY += (50 - goalieY) * 0.03; } // clamp CPU stick into right side of rink goalieX = Math.max(52, Math.min(94, goalieX)); goalieY = Math.max(10, Math.min(90, goalieY)); // collision with player stick — large hit zone, works any direction const sdx = puckX - stickX; const sdy = puckY - stickY; const sdist = Math.hypot(sdx, sdy); if (sdist < 12) { // hit it — bounce away from stick const ang = Math.atan2(sdy, sdx) || 0; const speed = Math.max(Math.hypot(puckVX, puckVY), diff.puckSpeed * 0.95) * 1.1; puckVX = Math.cos(ang) * speed; puckVY = Math.sin(ang) * speed; // push puck out of stick puckX = stickX + Math.cos(ang) * 12.5; puckY = stickY + Math.sin(ang) * 12.5; saves++; updateUI(); } // collision with CPU stick — push puck out cleanly to avoid getting pinned const cdx = puckX - goalieX; const cdy = puckY - goalieY; const cdist = Math.hypot(cdx, cdy); if (cdist < 12) { // direction from CPU stick to puck (away) let ang = Math.atan2(cdy, cdx); if (cdist < 0.5) ang = Math.atan2(50 - goalieY, 50 - goalieX); // fallback if exactly overlapping // bias bounce toward player goal (left) so puck doesn't get parked at right wall const biasAng = Math.atan2(Math.sin(ang) * 0.7, Math.cos(ang) - 0.3); const speed = Math.max(Math.hypot(puckVX, puckVY), diff.puckSpeed) * 1.1; puckVX = Math.cos(biasAng) * speed; puckVY = Math.sin(biasAng) * speed; puckX = goalieX + Math.cos(biasAng) * 13; puckY = goalieY + Math.sin(biasAng) * 13; } // GOAL detection // left goal (player's): x < 3 within goal height (vertical center ±14%) if (puckX 36 && puckY resetPuck('toYou'), 800); puckX = 50; puckY = 50; puckVX = 0; puckVY = 0; return scheduleNext(); } else { // wide / miss — bounce puckX = 3; puckVX = Math.abs(puckVX) * 0.8; shots++; updateUI(); } } // right goal (CPU's): x > 97 within goal height if (puckX > 97) { if (puckY > 36 && puckY resetPuck('toCpu'), 800); puckX = 50; puckY = 50; puckVX = 0; puckVY = 0; return scheduleNext(); } else { puckX = 97; puckVX = -Math.abs(puckVX) * 0.8; } } // friction & speed cap const speed = Math.hypot(puckVX, puckVY); const maxSpeed = diff.puckSpeed * 1.6; if (speed > maxSpeed) { puckVX *= maxSpeed / speed; puckVY *= maxSpeed / speed; } // render puck.style.left = puckX + '%'; puck.style.top = puckY + '%'; stick.style.left = stickX + '%'; stick.style.top = stickY + '%'; goalie.style.left = goalieX + '%'; goalie.style.top = goalieY + '%'; scheduleNext(); } function scheduleNext() { requestAnimationFrame(tick); } // init resetPuck('center'); scheduleNext(); })();