Files
ben.de-roo.org/haans/index.html
2026-03-16 14:49:18 +01:00

1150 lines
35 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LAUPAN LUCHCD PEILTJUS</title>
<link rel="icon" href="data:image/webp;base64,UklGRjgWAABXRUJQVlA4ICwWAADwVQCdASoVAbQAPp1GnEqlo6MhqtcJcLATiWhu3V44Qz92/..." type="image/webp">
</head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Chakra+Petch:wght@400;600;700&display=swap');
:root {
--crt-green: #39ff14;
--crt-amber: #ffb300;
--crt-red: #ff2244;
--crt-blue: #00d4ff;
--crt-pink: #ff00cc;
--bg: #020a04;
--panel: rgba(0,20,5,0.92);
}
* { margin:0; padding:0; box-sizing:border-box; }
html, body {
width:100%; height:100%;
background: var(--bg);
font-family: 'Press Start 2P', monospace;
overflow: hidden;
color: var(--crt-green);
}
/* CRT effects */
body::before {
content:'';
position:fixed; inset:0; z-index:9999; pointer-events:none;
background: repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(0,0,0,0.18) 3px, rgba(0,0,0,0.18) 4px);
}
body::after {
content:'';
position:fixed; inset:0; z-index:9998; pointer-events:none;
background: radial-gradient(ellipse at center, transparent 60%, rgba(0,0,0,0.75) 100%);
}
.crt-flicker {
animation: flicker 8s infinite;
}
@keyframes flicker {
0%,95%,100% { opacity:1; }
96% { opacity:0.97; }
97% { opacity:1; }
98% { opacity:0.95; }
99% { opacity:1; }
}
/* ===== SCREENS ===== */
.screen {
position:fixed; inset:0;
display:flex; flex-direction:column;
align-items:center; justify-content:center;
z-index:10;
transition: opacity 0.4s;
}
.screen.hidden { display:none; }
/* ===== ATTRACT / TITLE SCREEN ===== */
#screen-attract {
background: var(--bg);
gap: 28px;
}
.title-logo {
text-align:center;
line-height:1.6;
}
.title-top {
font-size: clamp(1rem, 3vw, 1.5rem);
color: var(--crt-amber);
text-shadow: 0 0 10px var(--crt-amber), 0 0 30px rgba(255,179,0,0.5);
letter-spacing: 0.15em;
animation: glow-amber 1.5s ease-in-out infinite alternate;
}
.title-main {
display:block;
font-size: clamp(1.8rem, 5vw, 3.2rem);
color: var(--crt-green);
text-shadow: 0 0 15px var(--crt-green), 0 0 40px rgba(57,255,20,0.4);
letter-spacing: 0.08em;
animation: glow-green 1.5s ease-in-out infinite alternate;
margin: 8px 0;
}
.title-sub {
font-size: clamp(0.5rem, 1.5vw, 0.75rem);
color: var(--crt-blue);
letter-spacing: 0.3em;
text-shadow: 0 0 8px var(--crt-blue);
}
@keyframes glow-green {
from { text-shadow: 0 0 10px var(--crt-green), 0 0 20px rgba(57,255,20,0.3); }
to { text-shadow: 0 0 20px var(--crt-green), 0 0 50px rgba(57,255,20,0.6), 0 0 80px rgba(57,255,20,0.2); }
}
@keyframes glow-amber {
from { text-shadow: 0 0 10px var(--crt-amber); }
to { text-shadow: 0 0 25px var(--crt-amber), 0 0 50px rgba(255,179,0,0.4); }
}
.dart-icon {
font-size: clamp(3rem, 8vw, 5rem);
animation: spin-dart 3s ease-in-out infinite;
filter: drop-shadow(0 0 12px var(--crt-green));
}
@keyframes spin-dart {
0%,100% { transform: rotate(-15deg) scale(1); }
50% { transform: rotate(15deg) scale(1.1); }
}
.insert-coin {
font-size: clamp(0.5rem, 1.5vw, 0.8rem);
color: var(--crt-amber);
letter-spacing: 0.2em;
animation: blink 0.9s step-end infinite;
text-shadow: 0 0 10px var(--crt-amber);
}
@keyframes blink { 0%,100%{opacity:1;} 50%{opacity:0;} }
.highscore-box {
background: rgba(57,255,20,0.05);
border: 2px solid rgba(57,255,20,0.3);
padding: 12px 30px;
text-align:center;
}
.hs-label { font-size:0.45rem; color:rgba(57,255,20,0.6); letter-spacing:0.2em; margin-bottom:6px; }
.hs-value { font-size:1.2rem; color:var(--crt-green); text-shadow:0 0 10px var(--crt-green); }
/* ===== MENU SCREEN ===== */
#screen-menu {
background: var(--bg);
gap: 30px;
}
.menu-title {
font-size: clamp(0.7rem, 2vw, 1rem);
color: var(--crt-amber);
text-shadow: 0 0 15px var(--crt-amber);
letter-spacing: 0.2em;
text-align:center;
}
.menu-question {
font-size: clamp(0.55rem, 1.5vw, 0.75rem);
color: var(--crt-blue);
letter-spacing: 0.15em;
text-align:center;
line-height:2;
}
.menu-choices {
display:flex;
gap: 30px;
flex-wrap:wrap;
justify-content:center;
}
.choice-card {
width: clamp(160px, 25vw, 220px);
background: rgba(0,30,10,0.9);
border: 3px solid rgba(57,255,20,0.3);
padding: 24px 16px;
text-align:center;
cursor:pointer;
transition: all 0.15s;
position:relative;
overflow:hidden;
}
.choice-card::before {
content:'';
position:absolute; inset:0;
background: linear-gradient(135deg, transparent 50%, rgba(57,255,20,0.03) 100%);
}
.choice-card:hover, .choice-card.selected {
border-color: var(--crt-green);
background: rgba(57,255,20,0.1);
box-shadow: 0 0 20px rgba(57,255,20,0.3), inset 0 0 20px rgba(57,255,20,0.05);
transform: scale(1.04);
}
.choice-icon { font-size: clamp(2rem, 5vw, 3rem); margin-bottom:14px; display:block; }
.choice-name {
font-size: clamp(0.5rem, 1.2vw, 0.7rem);
color: var(--crt-green);
letter-spacing:0.1em;
margin-bottom:8px;
}
.choice-desc {
font-family: 'Chakra Petch', sans-serif;
font-size: clamp(0.6rem, 1.2vw, 0.75rem);
color: rgba(57,255,20,0.6);
line-height:1.6;
}
.choice-key {
display:inline-block;
margin-top:10px;
font-size:0.45rem;
background: rgba(57,255,20,0.15);
border: 1px solid rgba(57,255,20,0.4);
padding: 4px 10px;
color: var(--crt-amber);
letter-spacing:0.1em;
}
/* ===== DIFFICULTY SCREEN ===== */
#screen-difficulty {
background: var(--bg);
gap:24px;
}
.diff-cards {
display:flex; gap:20px; flex-wrap:wrap; justify-content:center;
}
.diff-card {
width: clamp(140px, 20vw, 180px);
border: 3px solid rgba(57,255,20,0.3);
background: rgba(0,20,5,0.9);
padding: 20px 12px;
text-align:center;
cursor:pointer;
transition:all 0.15s;
}
.diff-card:hover {
border-color:var(--crt-amber);
box-shadow:0 0 20px rgba(255,179,0,0.3);
transform:scale(1.05);
}
.diff-name { font-size: clamp(0.5rem,1.2vw,0.7rem); margin-bottom:10px; }
.diff-easy { color:#00ff88; }
.diff-medium { color:var(--crt-amber); }
.diff-hard { color:var(--crt-red); }
.diff-stars { font-size:1rem; margin-bottom:8px; }
.diff-info {
font-family:'Chakra Petch',sans-serif;
font-size:clamp(0.55rem,1vw,0.68rem);
color:rgba(57,255,20,0.55);
line-height:1.6;
}
/* ===== GAME SCREEN ===== */
#screen-game {
background: var(--bg);
flex-direction:row;
align-items:stretch;
justify-content:center;
gap:0;
padding:0;
}
.side-panel {
width: clamp(120px, 18vw, 200px);
background: rgba(0,15,5,0.95);
border-right: 2px solid rgba(57,255,20,0.2);
display:flex; flex-direction:column;
align-items:center;
padding:20px 10px;
gap:20px;
flex-shrink:0;
}
.side-panel.right {
border-right:none;
border-left: 2px solid rgba(57,255,20,0.2);
}
.panel-label {
font-size:0.4rem;
letter-spacing:0.2em;
color:rgba(57,255,20,0.5);
text-transform:uppercase;
margin-bottom:4px;
}
.panel-value {
font-size:clamp(1.2rem,3vw,2rem);
color:var(--crt-green);
text-shadow:0 0 12px var(--crt-green);
}
.panel-box {
text-align:center;
width:100%;
background:rgba(57,255,20,0.04);
border:1px solid rgba(57,255,20,0.15);
padding:10px 6px;
}
.darts-display {
display:flex; flex-wrap:wrap; gap:4px; justify-content:center;
}
.dart-pip {
width:12px; height:12px;
background:var(--crt-amber);
border-radius:50% 50% 50% 0;
transform:rotate(-45deg);
box-shadow:0 0 6px var(--crt-amber);
transition:all 0.2s;
}
.dart-pip.used {
background:rgba(57,255,20,0.1);
border:1px solid rgba(57,255,20,0.2);
box-shadow:none;
}
.streak-fire {
font-size:1.5rem;
text-align:center;
animation:streakPulse 0.5s ease-in-out infinite alternate;
}
@keyframes streakPulse {
from { transform:scale(1); }
to { transform:scale(1.2); }
}
.mult-badge {
font-size:clamp(0.5rem,1.2vw,0.7rem);
padding:4px 10px;
background:rgba(255,0,204,0.2);
border:1px solid var(--crt-pink);
color:var(--crt-pink);
text-shadow:0 0 8px var(--crt-pink);
text-align:center;
}
/* Center arena */
.arena-wrap {
flex:1;
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
gap:12px;
padding:16px 8px;
min-width:0;
}
/* Phase indicator */
.phase-bar {
display:flex;
gap:10px;
align-items:center;
}
.phase-step {
font-size:0.45rem;
letter-spacing:0.1em;
padding:6px 14px;
border:2px solid rgba(57,255,20,0.2);
color:rgba(57,255,20,0.35);
transition:all 0.3s;
}
.phase-step.active {
border-color:var(--crt-amber);
color:var(--crt-amber);
text-shadow:0 0 10px var(--crt-amber);
background:rgba(255,179,0,0.1);
animation:phaseGlow 0.7s ease-in-out infinite alternate;
}
.phase-step.done {
border-color:var(--crt-green);
color:var(--crt-green);
text-shadow:0 0 8px var(--crt-green);
background:rgba(57,255,20,0.08);
}
@keyframes phaseGlow {
from { box-shadow:0 0 5px rgba(255,179,0,0.3); }
to { box-shadow:0 0 20px rgba(255,179,0,0.7); }
}
.phase-arrow { color:rgba(57,255,20,0.4); font-size:0.6rem; }
/* Arena */
.arena {
position:relative;
width: min(420px, 82vw, 72vh);
height: min(420px, 82vw, 72vh);
border:3px solid rgba(57,255,20,0.25);
background: radial-gradient(ellipse at center, #001a06 0%, #000a02 100%);
box-shadow: 0 0 40px rgba(57,255,20,0.1), inset 0 0 60px rgba(0,0,0,0.6);
overflow:hidden;
flex-shrink:0;
}
/* Horizontal line (X-axis targeting) */
.h-line {
position:absolute;
left:0; right:0;
height:2px;
background: linear-gradient(90deg, transparent, var(--crt-blue), transparent);
box-shadow:0 0 8px var(--crt-blue), 0 0 20px rgba(0,212,255,0.4);
pointer-events:none;
z-index:6;
transition: top 0s;
}
/* Vertical line (Y-axis targeting) */
.v-line {
position:absolute;
top:0; bottom:0;
width:2px;
background: linear-gradient(180deg, transparent, var(--crt-amber), transparent);
box-shadow:0 0 8px var(--crt-amber), 0 0 20px rgba(255,179,0,0.4);
pointer-events:none;
z-index:6;
}
.crosshair-dot {
position:absolute;
width:16px; height:16px;
border-radius:50%;
border:2px solid rgba(255,255,255,0.8);
transform:translate(-50%,-50%);
pointer-events:none;
z-index:7;
display:none;
box-shadow:0 0 10px rgba(255,255,255,0.6);
}
#dartboard {
position:absolute;
image-rendering:pixelated;
}
.dart-marker {
position:absolute;
width:14px; height:14px;
border-radius:50%;
transform:translate(-50%,-50%);
pointer-events:none;
z-index:10;
animation:dartLand 0.25s ease-out forwards;
}
@keyframes dartLand {
0% { transform:translate(-50%,-70%) scale(2.5); opacity:0; }
60% { transform:translate(-50%,-50%) scale(1.3); opacity:1; }
100% { transform:translate(-50%,-50%) scale(1); opacity:1; }
}
.score-popup {
position:absolute;
pointer-events:none;
font-family:'Press Start 2P',monospace;
animation:floatScore 1.4s ease-out forwards;
z-index:20;
text-shadow:0 2px 6px rgba(0,0,0,0.9);
white-space:nowrap;
transform-origin:center;
}
@keyframes floatScore {
0% { opacity:1; transform:translate(-50%,-50%) scale(0.4); }
25% { opacity:1; transform:translate(-50%,-70%) scale(1.1); }
80% { opacity:1; transform:translate(-50%,-150%) scale(0.9); }
100% { opacity:0; transform:translate(-50%,-200%) scale(0.7); }
}
.screen-flash {
position:absolute; inset:0;
pointer-events:none; z-index:30;
animation:flashOut 0.3s ease-out forwards;
}
@keyframes flashOut { 0%{opacity:1;} 100%{opacity:0;} }
/* Action hint */
.action-hint {
font-size:clamp(0.45rem,1.2vw,0.6rem);
letter-spacing:0.15em;
text-align:center;
padding:8px 20px;
border:2px solid rgba(57,255,20,0.3);
color:var(--crt-green);
background:rgba(0,30,5,0.8);
animation:hintPulse 1s ease-in-out infinite alternate;
min-height:36px;
display:flex; align-items:center; justify-content:center;
}
@keyframes hintPulse {
from { border-color:rgba(57,255,20,0.3); }
to { border-color:rgba(57,255,20,0.8); box-shadow:0 0 15px rgba(57,255,20,0.3); }
}
/* ===== GAME OVER SCREEN ===== */
#screen-gameover {
background:rgba(0,5,0,0.97);
gap:20px;
}
.go-title {
font-size:clamp(1.5rem,4vw,2.8rem);
color:var(--crt-red);
text-shadow:0 0 20px var(--crt-red), 0 0 50px rgba(255,34,68,0.4);
animation:glow-red 1s ease-in-out infinite alternate;
letter-spacing:0.15em;
}
@keyframes glow-red {
from { text-shadow:0 0 10px var(--crt-red); }
to { text-shadow:0 0 30px var(--crt-red), 0 0 60px rgba(255,34,68,0.5); }
}
.go-score-wrap { text-align:center; }
.go-label { font-size:0.45rem; color:rgba(57,255,20,0.6); letter-spacing:0.2em; margin-bottom:6px; }
.go-score { font-size:clamp(1.8rem,4vw,2.5rem); color:var(--crt-amber); text-shadow:0 0 15px var(--crt-amber); }
.go-best { font-size:0.6rem; color:var(--crt-green); letter-spacing:0.1em; margin-top:6px; }
.go-grade { font-size:clamp(1rem,3vw,1.8rem); letter-spacing:0.2em; }
.btn-row { display:flex; gap:16px; flex-wrap:wrap; justify-content:center; }
.arcade-btn {
font-family:'Press Start 2P',monospace;
font-size:clamp(0.45rem,1.2vw,0.6rem);
letter-spacing:0.12em;
padding:12px 24px;
border:3px solid;
background:transparent;
cursor:pointer;
transition:all 0.15s;
text-transform:uppercase;
}
.arcade-btn-green {
border-color:var(--crt-green);
color:var(--crt-green);
text-shadow:0 0 8px var(--crt-green);
}
.arcade-btn-green:hover {
background:rgba(57,255,20,0.15);
box-shadow:0 0 20px rgba(57,255,20,0.4);
transform:scale(1.05);
}
.arcade-btn-amber {
border-color:var(--crt-amber);
color:var(--crt-amber);
text-shadow:0 0 8px var(--crt-amber);
}
.arcade-btn-amber:hover {
background:rgba(255,179,0,0.15);
box-shadow:0 0 20px rgba(255,179,0,0.4);
transform:scale(1.05);
}
/* Controls reminder */
.controls-reminder {
font-family:'Chakra Petch',sans-serif;
font-size:clamp(0.6rem,1.2vw,0.75rem);
color:rgba(57,255,20,0.5);
letter-spacing:0.1em;
text-align:center;
line-height:2;
}
.key-badge {
display:inline-block;
background:rgba(57,255,20,0.1);
border:1px solid rgba(57,255,20,0.4);
padding:2px 8px;
color:var(--crt-green);
font-family:'Press Start 2P',monospace;
font-size:0.5rem;
}
/* Score history */
.score-history {
display:flex; flex-direction:column; gap:4px; width:100%; padding:0 4px;
}
.score-entry {
font-family:'Chakra Petch',sans-serif;
font-size:0.65rem;
display:flex; justify-content:space-between;
padding:4px 8px;
background:rgba(57,255,20,0.04);
border-left:3px solid rgba(57,255,20,0.3);
color:rgba(57,255,20,0.7);
letter-spacing:0.05em;
animation:slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { transform:translateX(-20px); opacity:0; }
to { transform:translateX(0); opacity:1; }
}
.score-entry .pts { color:var(--crt-amber); font-weight:700; }
</style>
</head>
<body class="crt-flicker">
<!-- ===== ATTRACT SCREEN ===== -->
<div class="screen" id="screen-attract">
<div class="dart-icon">🎯</div>
<div class="title-logo">
<div class="title-top">— ARCADE PRESENTS —</div>
<span class="title-main">LAUPAN LUCHCD</span>
<span class="title-main" style="font-size:clamp(1.2rem,3.5vw,2.2rem);color:var(--crt-amber);text-shadow:0 0 15px var(--crt-amber);">PEILTJUS</span>
<div class="title-sub" style="margin-top:10px;">THE ULTIMATE DART MACHINE</div>
</div>
<div class="highscore-box">
<div class="hs-label">— HIGH SCORE —</div>
<div class="hs-value" id="hs-display">00000</div>
</div>
<div class="insert-coin">[ DRUK OP SPATIE / KNOP OM TE STARTEN ]</div>
</div>
<!-- ===== MENU SCREEN (control choice) ===== -->
<div class="screen hidden" id="screen-menu">
<div class="menu-title">⚙ BESTURING KIEZEN</div>
<div class="menu-question">HOE WIL JE SPELEN?</div>
<div class="menu-choices">
<div class="choice-card" onclick="selectControl('buttons')" id="card-buttons">
<span class="choice-icon">⌨️</span>
<div class="choice-name">KNOPPEN</div>
<div class="choice-desc">Druk <strong>SPATIE</strong> of <strong>KNOP 1</strong><br>om de richtingen te stoppen</div>
<div class="choice-key">SPATIE / BTN1</div>
</div>
<div class="choice-card" onclick="selectControl('joystick')" id="card-joystick">
<span class="choice-icon">🕹️</span>
<div class="choice-name">JOYSTICK</div>
<div class="choice-desc">Druk de <strong>joystick in</strong><br>om de richtingen te stoppen</div>
<div class="choice-key">JS BUTTON</div>
</div>
</div>
<div class="controls-reminder">
OF KLIK OP DE KAART MET DE MUIS
</div>
</div>
<!-- ===== DIFFICULTY SCREEN ===== -->
<div class="screen hidden" id="screen-difficulty">
<div class="menu-title" style="color:var(--crt-amber);text-shadow:0 0 15px var(--crt-amber);">MOEILIJKHEID</div>
<div class="diff-cards">
<div class="diff-card" onclick="selectDiff('easy')">
<div class="diff-stars"></div>
<div class="diff-name diff-easy">MAKKELIJK</div>
<div class="diff-info">Langzame lijn<br>3 pijlen<br>Goede timing</div>
</div>
<div class="diff-card" onclick="selectDiff('medium')">
<div class="diff-stars">⭐⭐</div>
<div class="diff-name diff-medium">NORMAAL</div>
<div class="diff-info">Gemiddelde snelheid<br>3 pijlen<br>Let op!</div>
</div>
<div class="diff-card" onclick="selectDiff('hard')">
<div class="diff-stars">⭐⭐⭐</div>
<div class="diff-name diff-hard">MOEILIJK</div>
<div class="diff-info">Hoge snelheid<br>3 pijlen<br>Enkel voor pros</div>
</div>
</div>
</div>
<!-- ===== GAME SCREEN ===== -->
<div class="screen hidden" id="screen-game">
<!-- Left panel -->
<div class="side-panel left" id="left-panel">
<div class="panel-box">
<div class="panel-label">SCORE</div>
<div class="panel-value" id="score-disp">0</div>
</div>
<div class="panel-box">
<div class="panel-label">PIJLEN</div>
<div class="darts-display" id="darts-pips"></div>
</div>
<div class="panel-box">
<div class="panel-label">STREAK</div>
<div class="panel-value" id="streak-disp" style="color:var(--crt-amber);text-shadow:0 0 10px var(--crt-amber);">0</div>
<div class="streak-fire" id="streak-fire" style="display:none;">🔥</div>
</div>
<div class="mult-badge" id="mult-badge" style="display:none;">×1</div>
</div>
<!-- Center arena -->
<div class="arena-wrap">
<div class="phase-bar">
<div class="phase-step active" id="phase-x">① X-AS</div>
<div class="phase-arrow"></div>
<div class="phase-step" id="phase-y">② Y-AS</div>
<div class="phase-arrow"></div>
<div class="phase-step" id="phase-throw">③ GOOIEN</div>
</div>
<div class="arena" id="arena">
<canvas id="dartboard" width="420" height="420"></canvas>
<div class="h-line" id="h-line" style="top:50%"></div>
<div class="v-line" id="v-line" style="left:50%;display:none"></div>
<div class="crosshair-dot" id="crosshair-dot"></div>
</div>
<div class="action-hint" id="action-hint">WACHT...</div>
</div>
<!-- Right panel: score history -->
<div class="side-panel right" id="right-panel">
<div class="panel-label" style="color:var(--crt-green);">LAATSTE WORPEN</div>
<div class="score-history" id="score-history"></div>
<div style="margin-top:auto;width:100%;">
<div class="panel-box">
<div class="panel-label">BEST</div>
<div class="panel-value" id="best-disp" style="font-size:1rem;color:var(--crt-pink);text-shadow:0 0 10px var(--crt-pink);">0</div>
</div>
</div>
</div>
</div>
<!-- ===== GAME OVER SCREEN ===== -->
<div class="screen hidden" id="screen-gameover">
<div class="go-title">GAME OVER</div>
<div class="go-score-wrap">
<div class="go-label">— JOUW SCORE —</div>
<div class="go-score" id="go-score-val">0</div>
<div class="go-best" id="go-best-val"></div>
</div>
<div class="go-grade" id="go-grade"></div>
<div class="btn-row">
<button class="arcade-btn arcade-btn-green" onclick="goToMenu()">🕹 MENU</button>
<button class="arcade-btn arcade-btn-amber" onclick="restartGame()">↺ OPNIEUW</button>
</div>
<div class="controls-reminder">
SPATIE = OPNIEUW &nbsp;|&nbsp; ESC = MENU
</div>
</div>
<script>
// ============================================================
// STATE
// ============================================================
let currentScreen = 'attract';
let controlMode = 'buttons'; // 'buttons' | 'joystick'
let difficulty = 'medium';
let highScore = 0;
// Game state
let score=0, dartsLeft=8, streak=0, mult=1;
let phase = 'x'; // 'x' | 'y' | 'locked'
let lineXPos = 50; // % across arena (for v-line)
let lineYPos = 50; // % down arena (for h-line)
let lineXDir = 1, lineYDir = 1;
let lineXSpeed=1, lineYSpeed=0.8;
let animFrame=null, lastTime=0;
let gamepads={};
let jsButtonWasDown=false;
const diffSettings = {
easy: { speed:0.6, darts:3 },
medium: { speed:1.1, darts:3 },
hard: { speed:1.9, darts:3 },
};
// Dart board
const SEG_VALUES=[20,1,18,4,13,6,10,15,2,17,3,19,7,16,8,11,14,9,12,5];
// ============================================================
// SCREEN MANAGEMENT
// ============================================================
function showScreen(id) {
document.querySelectorAll('.screen').forEach(s=>s.classList.add('hidden'));
document.getElementById('screen-'+id).classList.remove('hidden');
currentScreen = id;
}
// ============================================================
// ATTRACT SCREEN
// ============================================================
function initAttract() {
document.getElementById('hs-display').textContent = String(highScore).padStart(5,'0');
showScreen('attract');
}
// ============================================================
// MENU (control choice)
// ============================================================
function openMenu() {
showScreen('menu');
}
function selectControl(mode) {
controlMode = mode;
document.getElementById('card-buttons').classList.toggle('selected', mode==='buttons');
document.getElementById('card-joystick').classList.toggle('selected', mode==='joystick');
setTimeout(()=>showScreen('difficulty'), 220);
}
// ============================================================
// DIFFICULTY
// ============================================================
function selectDiff(d) {
difficulty = d;
startGame();
}
// ============================================================
// DRAW BOARD
// ============================================================
function drawBoard() {
const canvas=document.getElementById('dartboard');
const ctx=canvas.getContext('2d');
const SIZE=canvas.width, r=SIZE*0.43, cx=SIZE/2, cy=SIZE/2;
const seg=20, aps=(Math.PI*2)/seg, off=-Math.PI/2-aps/2;
ctx.clearRect(0,0,SIZE,SIZE);
// Outer surround
ctx.beginPath(); ctx.arc(cx,cy,r+16,0,Math.PI*2);
ctx.fillStyle='#0d1a0d'; ctx.fill();
ctx.strokeStyle='rgba(57,255,20,0.5)'; ctx.lineWidth=3; ctx.stroke();
// Segments
for(let i=0;i<seg;i++){
const a1=off+i*aps, a2=a1+aps, dark=i%2===0;
const cDark='#141a14', cLight='#c8b870';
const cHit=dark?'#8b1a1a':'#1a6b1a', cHit2=dark?'#661111':'#115511';
// Single (11-62%)
ctx.beginPath(); ctx.moveTo(cx,cy);
ctx.arc(cx,cy,r*0.62,a1,a2); ctx.closePath();
ctx.fillStyle=dark?cDark:cLight; ctx.fill();
// Triple (62-68%)
ctx.beginPath();
ctx.arc(cx,cy,r*0.68,a1,a2); ctx.arc(cx,cy,r*0.62,a2,a1,true); ctx.closePath();
ctx.fillStyle=cHit; ctx.fill();
// Single mid (68-88%)
ctx.beginPath();
ctx.arc(cx,cy,r*0.88,a1,a2); ctx.arc(cx,cy,r*0.68,a2,a1,true); ctx.closePath();
ctx.fillStyle=dark?cDark:cLight; ctx.fill();
// Double (88-95%)
ctx.beginPath();
ctx.arc(cx,cy,r*0.95,a1,a2); ctx.arc(cx,cy,r*0.88,a2,a1,true); ctx.closePath();
ctx.fillStyle=cHit2; ctx.fill();
// Number
const ma=a1+aps/2;
const nr=r*1.05;
ctx.save();
ctx.translate(cx+Math.cos(ma)*nr, cy+Math.sin(ma)*nr);
ctx.rotate(ma+Math.PI/2);
ctx.font=`bold ${Math.round(r*0.08)}px 'Press Start 2P',monospace`;
ctx.fillStyle='#fff'; ctx.textAlign='center'; ctx.textBaseline='middle';
ctx.shadowColor='rgba(0,0,0,0.9)'; ctx.shadowBlur=4;
ctx.fillText(SEG_VALUES[i],0,0);
ctx.restore();
}
// Wire lines
for(let i=0;i<seg;i++){
const a=off+i*aps;
ctx.beginPath();
ctx.moveTo(cx+Math.cos(a)*r*0.11, cy+Math.sin(a)*r*0.11);
ctx.lineTo(cx+Math.cos(a)*r*0.95, cy+Math.sin(a)*r*0.95);
ctx.strokeStyle='rgba(0,0,0,0.7)'; ctx.lineWidth=1.5; ctx.stroke();
}
[0.95,0.88,0.68,0.62,0.11].forEach(f=>{
ctx.beginPath(); ctx.arc(cx,cy,r*f,0,Math.PI*2);
ctx.strokeStyle='rgba(100,80,30,0.9)'; ctx.lineWidth=1.5; ctx.stroke();
});
// Bull outer
ctx.beginPath(); ctx.arc(cx,cy,r*0.11,0,Math.PI*2);
ctx.fillStyle='#1d7a1d'; ctx.fill();
// Bullseye
ctx.beginPath(); ctx.arc(cx,cy,r*0.05,0,Math.PI*2);
const bg=ctx.createRadialGradient(cx-2,cy-2,0,cx,cy,r*0.05);
bg.addColorStop(0,'#ff8800'); bg.addColorStop(1,'#cc0000');
ctx.fillStyle=bg; ctx.fill();
ctx.strokeStyle='rgba(255,255,255,0.7)'; ctx.lineWidth=1.5; ctx.stroke();
// Position canvas centered
const arena=document.getElementById('arena');
const aw=arena.clientWidth, ah=arena.clientHeight;
canvas.style.left=(aw/2-canvas.offsetWidth/2)+'px';
canvas.style.top =(ah/2-canvas.offsetHeight/2)+'px';
}
// ============================================================
// GAME
// ============================================================
function startGame() {
const s = diffSettings[difficulty];
score=0; streak=0; mult=1; dartsLeft=s.darts;
phase='x'; lineXPos=50; lineYPos=Math.random()*70+15;
lineXDir=Math.random()>0.5?1:-1; lineYDir=Math.random()>0.5?1:-1;
lineXSpeed = s.speed * (0.8+Math.random()*0.4);
lineYSpeed = s.speed * (0.8+Math.random()*0.4);
jsButtonWasDown=false;
showScreen('game');
drawBoard();
updateHUD();
updatePhaseUI();
updateDartPips();
document.getElementById('score-history').innerHTML='';
document.getElementById('best-disp').textContent=highScore;
// Position lines
document.getElementById('h-line').style.top = lineYPos+'%';
document.getElementById('h-line').style.display='block';
document.getElementById('v-line').style.left = lineXPos+'%';
document.getElementById('v-line').style.display='none';
document.getElementById('crosshair-dot').style.display='none';
cancelAnimationFrame(animFrame);
lastTime=0;
animFrame=requestAnimationFrame(gameLoop);
setHint();
}
function gameLoop(ts) {
if(currentScreen!=='game'){ return; }
const dt = lastTime ? Math.min((ts-lastTime)/16.67, 3) : 1;
lastTime = ts;
// Joystick polling
if(controlMode==='joystick') pollGamepad();
if(phase==='x'){
// Move Y-line (horizontal bar) up/down for X-aim
lineYPos += lineYDir * lineYSpeed * dt;
if(lineYPos<=3||lineYPos>=97){ lineYDir*=-1; lineYPos=Math.max(3,Math.min(97,lineYPos)); }
document.getElementById('h-line').style.top=lineYPos+'%';
} else if(phase==='y'){
// Move X-line (vertical bar) left/right for Y-aim
lineXPos += lineXDir * lineXSpeed * dt;
if(lineXPos<=3||lineXPos>=97){ lineXDir*=-1; lineXPos=Math.max(3,Math.min(97,lineXPos)); }
document.getElementById('v-line').style.left=lineXPos+'%';
// Update crosshair position
const ch=document.getElementById('crosshair-dot');
ch.style.left=lineXPos+'%'; ch.style.top=lineYPos+'%';
}
animFrame=requestAnimationFrame(gameLoop);
}
function pollGamepad() {
const gps=navigator.getGamepads?navigator.getGamepads():[];
for(const gp of gps){
if(!gp) continue;
const btnDown = gp.buttons[0]?.pressed||gp.buttons[1]?.pressed||gp.buttons[2]?.pressed||gp.buttons[3]?.pressed;
if(btnDown && !jsButtonWasDown){ fireAction(); }
jsButtonWasDown = btnDown;
}
}
function fireAction() {
if(currentScreen==='attract'){ openMenu(); return; }
if(currentScreen==='gameover'){ restartGame(); return; }
if(currentScreen!=='game') return;
if(phase==='x'){
// Lock Y position (horizontal line)
phase='y';
document.getElementById('v-line').style.display='block';
document.getElementById('v-line').style.left=lineXPos+'%';
document.getElementById('crosshair-dot').style.display='block';
document.getElementById('crosshair-dot').style.left=lineXPos+'%';
document.getElementById('crosshair-dot').style.top=lineYPos+'%';
// Speed up Y a bit
lineYSpeed *= 1.15;
updatePhaseUI();
setHint();
} else if(phase==='y'){
// Lock X → throw dart
phase='locked';
updatePhaseUI();
throwDart();
}
}
function throwDart() {
const arena=document.getElementById('arena');
const aw=arena.clientWidth, ah=arena.clientHeight;
const ax=lineXPos/100*aw, ay=lineYPos/100*ah;
// Board center
const canvas=document.getElementById('dartboard');
const bcx=canvas.offsetLeft+canvas.offsetWidth/2;
const bcy=canvas.offsetTop+canvas.offsetHeight/2;
const scale=canvas.width/canvas.offsetWidth;
const dx=(ax-bcx)*scale, dy=(ay-bcy)*scale;
const zone=getZone(dx,dy);
const hit=zone.pts>0;
// Update streak & mult
if(hit){ streak++; } else { streak=0; }
mult = streak>=6?3 : streak>=3?2 : 1;
const pts=zone.pts*mult;
// Dart marker
const m=document.createElement('div');
m.className='dart-marker';
m.style.left=ax+'px'; m.style.top=ay+'px';
if(hit){
m.style.background='radial-gradient(circle,#fff 25%,#ffdd00 55%,transparent 100%)';
m.style.boxShadow='0 0 10px #ffdd00, 0 0 25px rgba(255,200,0,0.5)';
} else {
m.style.background='radial-gradient(circle,#ff4444 30%,rgba(255,0,0,0.2) 100%)';
m.style.boxShadow='0 0 8px #ff4444';
}
arena.appendChild(m);
setTimeout(()=>m.remove(),3000);
// Flash
const fl=document.createElement('div');
fl.className='screen-flash';
fl.style.background=hit?'rgba(57,255,20,0.12)':'rgba(255,34,68,0.25)';
arena.appendChild(fl); setTimeout(()=>fl.remove(),300);
// Score popup
const pop=document.createElement('div');
pop.className='score-popup';
pop.style.left=ax+'px'; pop.style.top=ay+'px';
pop.style.color=zone.color;
let lbl=zone.label;
if(hit&&mult>1) lbl+=' ×'+mult+'!';
pop.textContent=hit?`+${pts} ${lbl}`:lbl;
pop.style.fontSize=pts>=100?'0.75rem':pts>=50?'0.6rem':'0.5rem';
arena.appendChild(pop);
setTimeout(()=>pop.remove(),1400);
// Score history
addScoreEntry(zone.label, pts, hit);
score+=pts; dartsLeft--;
updateHUD();
updateDartPips();
if(dartsLeft<=0){
setTimeout(endGame, 600);
return;
}
// Reset for next throw
setTimeout(()=>{
phase='x';
lineYPos=Math.random()*70+15;
lineXPos=50;
const s=diffSettings[difficulty];
const spd=s.speed*(1+score/400);
lineYSpeed=spd*(0.8+Math.random()*0.4);
lineXSpeed=spd*(0.8+Math.random()*0.4);
document.getElementById('v-line').style.display='none';
document.getElementById('crosshair-dot').style.display='none';
document.getElementById('h-line').style.top=lineYPos+'%';
updatePhaseUI();
setHint();
}, 350);
}
function getZone(dx,dy){
const r=420*0.43, dist=Math.sqrt(dx*dx+dy*dy), frac=dist/r;
if(frac>0.95) return {pts:0,label:'MISS',color:'#ff4444'};
const aps=(Math.PI*2)/20, off=-Math.PI/2-aps/2;
let ang=((Math.atan2(dy,dx)-off)%(Math.PI*2)+Math.PI*2)%(Math.PI*2);
const si=Math.floor(ang/aps)%20, v=SEG_VALUES[si];
if(frac<=0.05) return {pts:50,label:'BULLSEYE!',color:'#ff8800'};
if(frac<=0.11) return {pts:25,label:'BULL',color:'#ff4400'};
if(frac>0.62&&frac<=0.68) return {pts:v*3,label:`T${v}`,color:'#00ffcc'};
if(frac>0.88&&frac<=0.95) return {pts:v*2,label:`D${v}`,color:'#ffdd00'};
return {pts:v,label:`${v}`,color:'#fff'};
}
function addScoreEntry(label, pts, hit){
const hist=document.getElementById('score-history');
const e=document.createElement('div');
e.className='score-entry';
e.innerHTML=`<span>${hit?'✓':'✗'} ${label}</span><span class="pts">${hit?'+'+pts:'0'}</span>`;
if(!hit) e.style.borderLeftColor='var(--crt-red)';
hist.insertBefore(e, hist.firstChild);
// Keep max 7
while(hist.children.length>7) hist.removeChild(hist.lastChild);
}
function updateHUD(){
document.getElementById('score-disp').textContent=score;
const sv=document.getElementById('streak-disp');
sv.textContent=streak;
document.getElementById('streak-fire').style.display=streak>=3?'block':'none';
const mb=document.getElementById('mult-badge');
if(mult>1){
mb.style.display='block';
mb.textContent='×'+mult+' STREAK!';
} else { mb.style.display='none'; }
}
function updateDartPips(){
const container=document.getElementById('darts-pips');
container.innerHTML='';
const total=diffSettings[difficulty].darts;
for(let i=0;i<total;i++){
const pip=document.createElement('div');
pip.className='dart-pip'+(i>=dartsLeft?' used':'');
container.appendChild(pip);
}
}
function updatePhaseUI(){
const px=document.getElementById('phase-x');
const py=document.getElementById('phase-y');
const pt=document.getElementById('phase-throw');
px.className='phase-step'; py.className='phase-step'; pt.className='phase-step';
if(phase==='x') { px.classList.add('active'); }
else if(phase==='y') { px.classList.add('done'); py.classList.add('active'); }
else { px.classList.add('done'); py.classList.add('done'); pt.classList.add('active'); }
}
function setHint(){
const el=document.getElementById('action-hint');
const key=controlMode==='joystick'?'[JS KNOP]':'[SPATIE]';
if(phase==='x') el.textContent=`${key} = STOP X-AS (horizontale lijn)`;
else if(phase==='y') el.textContent=`${key} = STOP Y-AS (verticale lijn)`;
else el.textContent='GOOIEN...';
}
function endGame(){
cancelAnimationFrame(animFrame);
const isNew=score>highScore;
if(isNew) highScore=score;
document.getElementById('go-score-val').textContent=score;
document.getElementById('go-best-val').textContent = isNew?'★ NIEUW RECORD! ★':`Beste: ${highScore}`;
document.getElementById('go-best-val').style.color = isNew?'var(--crt-amber)':'var(--crt-green)';
// Grade
const grades=[
{min:300,g:'S',c:'#ffdd00'},{min:200,g:'A',c:'#00ffcc'},
{min:120,g:'B',c:'#39ff14'},{min:60,g:'C',c:'#ffb300'},
{min:0,g:'D',c:'#ff4444'}
];
const gr=grades.find(x=>score>=x.min);
const gradeEl=document.getElementById('go-grade');
gradeEl.textContent=`RANG: ${gr.g}`;
gradeEl.style.color=gr.c;
gradeEl.style.textShadow=`0 0 20px ${gr.c}`;
showScreen('gameover');
}
function goToMenu(){ cancelAnimationFrame(animFrame); initAttract(); }
function restartGame(){ startGame(); }
// ============================================================
// INPUT
// ============================================================
document.addEventListener('keydown', e=>{
const fire = e.code==='Space'||e.code==='Enter'||e.code==='KeyZ'||e.code==='KeyX';
if(fire){ e.preventDefault(); }
if(currentScreen==='attract' && fire){ openMenu(); return; }
if(currentScreen==='gameover'){
if(fire||e.code==='Space') restartGame();
if(e.code==='Escape') goToMenu();
return;
}
if(currentScreen==='game' && fire){ fireAction(); return; }
});
// Gamepad
window.addEventListener('gamepadconnected', e=>{ gamepads[e.gamepad.index]=e.gamepad; });
window.addEventListener('gamepaddisconnected', e=>{ delete gamepads[e.gamepad.index]; });
// ============================================================
// INIT
// ============================================================
initAttract();
</script>
</body>
</html>