Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/Jennys-Drumste / drum-teacher-pro.html |
| Date | Not specified in source |
| Repository | Janady13/Jennys-Drumste |
| Revision | 1f946b9e18e552f80f4b2ffd7f59c01d0126aa53 |
| Branch | claude/jenny-drumsets-research-MofSb |
| Path or archive member | drum-teacher-pro.html |
| Original SHA-256 | cb2280313c4a369b914802b8cd1fdc59a2d2d84b12563414e138caf0f865bd9d |
| Git blob SHA | 9ba6dfad3fc0301c7eb6934dc5d36c1aff30c9e2 |
| Reading-copy SHA-256 | cb2280313c4a369b914802b8cd1fdc59a2d2d84b12563414e138caf0f865bd9d |
Recovered source transcript
Download complete source text ↓2,033 lines · 69.1 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DrumMaster Pro - SEFERIM AGI Rock Band Style</title>
<script src="seferim-core.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Russo+One&family=Press+Start+2P&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--kick-color: #ff3366;
--snare-color: #ffcc00;
--hihat-color: #00ccff;
--crash-color: #ff9900;
--tom1-color: #cc33ff;
--tom2-color: #33ff66;
--tom3-color: #ff6633;
--ride-color: #3366ff;
--primary: #00ffcc;
--secondary: #ff00ff;
--accent: #ffcc00;
--dark: #0a0a15;
--success: #00ff88;
--danger: #ff3366;
}
body {
font-family: 'Orbitron', sans-serif;
background: var(--dark);
color: white;
overflow: hidden;
height: 100vh;
user-select: none;
}
/* Main Game Container */
.game-container {
display: grid;
grid-template-columns: 200px 1fr 280px;
height: 100vh;
gap: 0;
}
/* Left Panel - Stats */
.stats-panel {
background: linear-gradient(180deg, rgba(0,0,0,0.9) 0%, rgba(20,0,40,0.9) 100%);
padding: 20px;
display: flex;
flex-direction: column;
gap: 15px;
border-right: 2px solid rgba(255,0,255,0.3);
}
.logo {
font-family: 'Russo One', sans-serif;
font-size: 1.2em;
text-align: center;
background: linear-gradient(135deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 10px;
}
/* Score Display */
.score-container {
background: rgba(0,0,0,0.5);
border: 2px solid var(--primary);
border-radius: 10px;
padding: 15px;
text-align: center;
}
.score-label {
font-size: 0.7em;
color: rgba(255,255,255,0.6);
text-transform: uppercase;
letter-spacing: 2px;
}
.score-value {
font-family: 'Press Start 2P', monospace;
font-size: 1.5em;
color: var(--accent);
text-shadow: 0 0 20px var(--accent);
}
/* Multiplier */
.multiplier-container {
text-align: center;
padding: 10px;
}
.multiplier-label {
font-size: 0.6em;
color: rgba(255,255,255,0.5);
}
.multiplier-value {
font-size: 2.5em;
font-weight: 900;
background: linear-gradient(135deg, var(--accent), var(--danger));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 30px rgba(255,204,0,0.5);
}
.multiplier-bar {
height: 8px;
background: rgba(255,255,255,0.1);
border-radius: 4px;
margin-top: 5px;
overflow: hidden;
}
.multiplier-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--accent));
border-radius: 4px;
transition: width 0.2s;
}
/* Combo Display */
.combo-container {
text-align: center;
padding: 15px;
background: rgba(0,0,0,0.3);
border-radius: 10px;
}
.combo-value {
font-size: 3em;
font-weight: 900;
color: var(--success);
text-shadow: 0 0 30px var(--success);
transition: transform 0.1s;
}
.combo-value.bump {
transform: scale(1.3);
}
.combo-label {
font-size: 0.8em;
color: var(--success);
letter-spacing: 3px;
}
/* Star Power */
.star-power-container {
padding: 15px;
background: linear-gradient(135deg, rgba(255,204,0,0.1), rgba(255,102,0,0.1));
border: 2px solid rgba(255,204,0,0.3);
border-radius: 10px;
text-align: center;
}
.star-power-label {
font-size: 0.7em;
color: var(--accent);
letter-spacing: 2px;
margin-bottom: 8px;
}
.star-power-bar {
height: 20px;
background: rgba(0,0,0,0.5);
border-radius: 10px;
overflow: hidden;
position: relative;
}
.star-power-fill {
height: 100%;
background: linear-gradient(90deg, #ff6600, #ffcc00, #ff6600);
background-size: 200% 100%;
animation: starPowerShine 2s linear infinite;
transition: width 0.3s;
}
.star-power-fill.active {
animation: starPowerActive 0.5s linear infinite;
}
@keyframes starPowerShine {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@keyframes starPowerActive {
0%, 100% { filter: brightness(1); }
50% { filter: brightness(1.5); }
}
.star-power-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 0.6em;
font-weight: bold;
color: white;
text-shadow: 0 0 5px black;
}
/* Accuracy */
.accuracy-container {
text-align: center;
padding: 10px;
}
.accuracy-value {
font-size: 1.8em;
font-weight: bold;
}
.accuracy-value.perfect { color: var(--success); }
.accuracy-value.great { color: var(--primary); }
.accuracy-value.good { color: var(--accent); }
.accuracy-value.poor { color: var(--danger); }
/* Center Panel - Note Highway */
.highway-panel {
position: relative;
background: linear-gradient(180deg, #000 0%, #0a0020 50%, #150030 100%);
overflow: hidden;
}
/* Stage Background */
.stage-background {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 30%;
background: radial-gradient(ellipse at center bottom, rgba(255,0,255,0.2) 0%, transparent 70%);
pointer-events: none;
}
.stage-lights {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 200px;
display: flex;
justify-content: space-around;
pointer-events: none;
}
.stage-light {
width: 100px;
height: 300px;
background: linear-gradient(180deg, rgba(255,255,255,0.3) 0%, transparent 100%);
clip-path: polygon(30% 0%, 70% 0%, 100% 100%, 0% 100%);
opacity: 0.3;
animation: lightPulse 2s ease-in-out infinite;
}
.stage-light:nth-child(1) { animation-delay: 0s; background: linear-gradient(180deg, rgba(255,0,100,0.4) 0%, transparent 100%); }
.stage-light:nth-child(2) { animation-delay: 0.5s; background: linear-gradient(180deg, rgba(0,255,200,0.4) 0%, transparent 100%); }
.stage-light:nth-child(3) { animation-delay: 1s; background: linear-gradient(180deg, rgba(255,200,0,0.4) 0%, transparent 100%); }
.stage-light:nth-child(4) { animation-delay: 1.5s; background: linear-gradient(180deg, rgba(100,0,255,0.4) 0%, transparent 100%); }
@keyframes lightPulse {
0%, 100% { opacity: 0.2; }
50% { opacity: 0.5; }
}
/* Note Highway Canvas */
.highway-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* Hit Zone */
.hit-zone {
position: absolute;
bottom: 80px;
left: 0;
right: 0;
height: 60px;
display: flex;
justify-content: center;
gap: 10px;
z-index: 10;
}
.hit-pad {
width: 80px;
height: 60px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.6em;
font-weight: bold;
text-transform: uppercase;
transition: all 0.05s;
position: relative;
overflow: hidden;
}
.hit-pad::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: inherit;
filter: blur(20px);
opacity: 0.5;
z-index: -1;
}
.hit-pad[data-drum="kick"] { background: linear-gradient(180deg, var(--kick-color), #990033); border: 3px solid var(--kick-color); }
.hit-pad[data-drum="snare"] { background: linear-gradient(180deg, var(--snare-color), #997700); border: 3px solid var(--snare-color); }
.hit-pad[data-drum="hihat"] { background: linear-gradient(180deg, var(--hihat-color), #006699); border: 3px solid var(--hihat-color); }
.hit-pad[data-drum="crash"] { background: linear-gradient(180deg, var(--crash-color), #995500); border: 3px solid var(--crash-color); }
.hit-pad[data-drum="tom1"] { background: linear-gradient(180deg, var(--tom1-color), #660099); border: 3px solid var(--tom1-color); }
.hit-pad[data-drum="tom2"] { background: linear-gradient(180deg, var(--tom2-color), #009933); border: 3px solid var(--tom2-color); }
.hit-pad[data-drum="ride"] { background: linear-gradient(180deg, var(--ride-color), #003399); border: 3px solid var(--ride-color); }
.hit-pad.hit {
transform: scale(1.2);
box-shadow: 0 0 40px currentColor;
}
.hit-pad .key-hint {
position: absolute;
bottom: -20px;
font-size: 0.7em;
color: rgba(255,255,255,0.5);
}
/* Hit Effects Container */
.effects-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 20;
}
/* Explosion Effect */
.explosion {
position: absolute;
width: 150px;
height: 150px;
pointer-events: none;
animation: explode 0.4s ease-out forwards;
}
@keyframes explode {
0% { transform: scale(0.5); opacity: 1; }
100% { transform: scale(2); opacity: 0; }
}
/* Hit Judgment */
.hit-judgment {
position: absolute;
font-family: 'Russo One', sans-serif;
font-size: 2em;
font-weight: bold;
text-shadow: 0 0 20px currentColor;
animation: judgeFloat 0.6s ease-out forwards;
z-index: 30;
}
.hit-judgment.perfect { color: var(--success); }
.hit-judgment.great { color: var(--primary); }
.hit-judgment.good { color: var(--accent); }
.hit-judgment.miss { color: var(--danger); }
@keyframes judgeFloat {
0% { transform: translateY(0) scale(1.5); opacity: 1; }
100% { transform: translateY(-80px) scale(1); opacity: 0; }
}
/* Streak Flames */
.streak-flames {
position: absolute;
bottom: 140px;
left: 50%;
transform: translateX(-50%);
width: 600px;
height: 100px;
display: flex;
justify-content: center;
gap: 5px;
opacity: 0;
transition: opacity 0.3s;
z-index: 5;
}
.streak-flames.active {
opacity: 1;
}
.flame {
width: 30px;
background: linear-gradient(180deg, transparent, var(--accent), var(--danger));
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
animation: flameFlicker 0.2s ease-in-out infinite alternate;
}
@keyframes flameFlicker {
0% { transform: scaleY(0.8) scaleX(1); opacity: 0.8; }
100% { transform: scaleY(1.2) scaleX(0.9); opacity: 1; }
}
/* Crowd Meter */
.crowd-meter {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
width: 400px;
z-index: 30;
}
.crowd-label {
text-align: center;
font-size: 0.7em;
color: rgba(255,255,255,0.6);
margin-bottom: 5px;
}
.crowd-bar {
height: 15px;
background: rgba(0,0,0,0.5);
border-radius: 8px;
overflow: hidden;
border: 2px solid rgba(255,255,255,0.2);
}
.crowd-fill {
height: 100%;
transition: width 0.5s, background 0.3s;
border-radius: 6px;
}
.crowd-fill.danger { background: linear-gradient(90deg, #ff0000, #ff3333); }
.crowd-fill.warning { background: linear-gradient(90deg, #ff6600, #ffcc00); }
.crowd-fill.good { background: linear-gradient(90deg, #00cc66, #00ff88); }
.crowd-fill.great { background: linear-gradient(90deg, #00ffcc, #00ff88); animation: crowdPulse 0.5s infinite; }
@keyframes crowdPulse {
0%, 100% { filter: brightness(1); }
50% { filter: brightness(1.3); }
}
/* Song Info */
.song-info {
position: absolute;
top: 50px;
left: 50%;
transform: translateX(-50%);
text-align: center;
z-index: 30;
}
.song-title {
font-family: 'Russo One', sans-serif;
font-size: 1.5em;
color: white;
text-shadow: 0 0 20px var(--primary);
}
.song-artist {
font-size: 0.9em;
color: rgba(255,255,255,0.6);
}
/* Controls Bar */
.controls-bar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background: linear-gradient(180deg, transparent, rgba(0,0,0,0.9));
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
z-index: 30;
}
.control-btn {
padding: 10px 25px;
font-family: 'Orbitron', sans-serif;
font-size: 0.9em;
border: 2px solid var(--primary);
background: rgba(0,255,204,0.1);
color: var(--primary);
border-radius: 25px;
cursor: pointer;
transition: all 0.2s;
}
.control-btn:hover {
background: var(--primary);
color: var(--dark);
box-shadow: 0 0 30px var(--primary);
}
.control-btn.active {
background: var(--primary);
color: var(--dark);
}
/* BPM Display */
.bpm-display {
font-size: 1.2em;
color: var(--accent);
}
/* Right Panel - SEFERIM & Settings */
.right-panel {
background: linear-gradient(180deg, rgba(0,0,0,0.9) 0%, rgba(0,20,40,0.9) 100%);
padding: 15px;
display: flex;
flex-direction: column;
gap: 15px;
border-left: 2px solid rgba(0,255,204,0.3);
overflow-y: auto;
}
/* Lesson Selector */
.lesson-selector {
background: rgba(0,0,0,0.3);
border-radius: 10px;
padding: 15px;
}
.lesson-label {
font-size: 0.7em;
color: rgba(255,255,255,0.5);
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.lesson-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.lesson-btn {
padding: 10px;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 8px;
color: white;
font-size: 0.7em;
cursor: pointer;
transition: all 0.2s;
text-align: left;
}
.lesson-btn:hover {
background: rgba(0,255,204,0.1);
border-color: var(--primary);
}
.lesson-btn.active {
background: rgba(0,255,204,0.2);
border-color: var(--primary);
box-shadow: 0 0 15px rgba(0,255,204,0.3);
}
.lesson-btn .difficulty {
display: inline-block;
margin-top: 5px;
}
.lesson-btn .difficulty span {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(255,255,255,0.2);
margin-right: 2px;
}
.lesson-btn .difficulty span.filled {
background: var(--accent);
}
/* SEFERIM Panel */
.seferim-panel {
background: linear-gradient(135deg, rgba(10, 10, 20, 0.95), rgba(0, 20, 40, 0.95));
border: 1px solid rgba(0, 255, 204, 0.3);
border-radius: 12px;
padding: 15px;
}
.seferim-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.seferim-title {
font-size: 0.8em;
color: var(--primary);
letter-spacing: 1px;
}
.seferim-version {
font-size: 0.6em;
color: rgba(0,255,204,0.5);
background: rgba(0,255,204,0.1);
padding: 2px 6px;
border-radius: 6px;
}
.seferim-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-bottom: 12px;
}
.seferim-stat {
text-align: center;
padding: 8px;
background: rgba(0,0,0,0.3);
border-radius: 6px;
}
.seferim-stat-value {
font-size: 1.1em;
color: var(--primary);
font-weight: bold;
}
.seferim-stat-label {
font-size: 0.55em;
color: rgba(255,255,255,0.4);
text-transform: uppercase;
}
.families-mini {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 3px;
}
.family-bar {
height: 25px;
background: rgba(0,255,204,0.1);
border-radius: 3px;
position: relative;
overflow: hidden;
}
.family-bar-fill {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(180deg, var(--primary), rgba(0,255,204,0.5));
transition: height 0.3s;
}
.family-bar.active {
box-shadow: 0 0 10px var(--secondary);
}
/* Tempo Control */
.tempo-control {
background: rgba(0,0,0,0.3);
border-radius: 10px;
padding: 15px;
}
.tempo-label {
font-size: 0.7em;
color: rgba(255,255,255,0.5);
margin-bottom: 8px;
}
.tempo-slider {
width: 100%;
height: 8px;
-webkit-appearance: none;
background: rgba(255,255,255,0.1);
border-radius: 4px;
outline: none;
}
.tempo-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: var(--primary);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 0 10px var(--primary);
}
.tempo-value {
text-align: center;
font-size: 1.5em;
color: var(--accent);
margin-top: 8px;
}
/* MIDI Status */
.midi-status {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background: rgba(0,0,0,0.3);
border-radius: 8px;
}
.midi-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--danger);
}
.midi-dot.connected {
background: var(--success);
animation: midiPulse 1s infinite;
}
@keyframes midiPulse {
0%, 100% { box-shadow: 0 0 5px var(--success); }
50% { box-shadow: 0 0 15px var(--success); }
}
.midi-text {
font-size: 0.75em;
color: rgba(255,255,255,0.7);
}
/* Practice Mode Toggle */
.practice-mode {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
background: rgba(0,0,0,0.3);
border-radius: 8px;
}
.practice-label {
font-size: 0.75em;
color: rgba(255,255,255,0.7);
}
.toggle-switch {
width: 50px;
height: 26px;
background: rgba(255,255,255,0.1);
border-radius: 13px;
position: relative;
cursor: pointer;
transition: background 0.3s;
}
.toggle-switch.active {
background: var(--primary);
}
.toggle-switch::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: left 0.3s;
}
.toggle-switch.active::after {
left: 27px;
}
/* Countdown Overlay */
.countdown-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.8);
display: none;
align-items: center;
justify-content: center;
z-index: 100;
}
.countdown-overlay.active {
display: flex;
}
.countdown-number {
font-family: 'Press Start 2P', monospace;
font-size: 8em;
color: var(--primary);
text-shadow: 0 0 50px var(--primary);
animation: countdownPulse 1s ease-out;
}
@keyframes countdownPulse {
0% { transform: scale(2); opacity: 0; }
50% { transform: scale(1); opacity: 1; }
100% { transform: scale(0.8); opacity: 0; }
}
/* Game Over Screen */
.game-over-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.9);
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 100;
}
.game-over-overlay.active {
display: flex;
}
.results-card {
background: linear-gradient(135deg, rgba(20,0,40,0.95), rgba(0,40,60,0.95));
border: 2px solid var(--primary);
border-radius: 20px;
padding: 40px;
text-align: center;
max-width: 500px;
}
.results-title {
font-family: 'Russo One', sans-serif;
font-size: 2.5em;
margin-bottom: 20px;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.results-score {
font-family: 'Press Start 2P', monospace;
font-size: 3em;
color: var(--accent);
margin-bottom: 30px;
}
.results-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.result-stat {
text-align: center;
}
.result-stat-value {
font-size: 2em;
font-weight: bold;
color: var(--primary);
}
.result-stat-label {
font-size: 0.8em;
color: rgba(255,255,255,0.6);
}
.star-rating {
font-size: 3em;
margin-bottom: 20px;
}
.star-rating .star {
color: rgba(255,255,255,0.2);
margin: 0 5px;
transition: color 0.3s;
}
.star-rating .star.filled {
color: var(--accent);
text-shadow: 0 0 20px var(--accent);
}
/* Responsive */
@media (max-width: 1200px) {
.game-container {
grid-template-columns: 150px 1fr 200px;
}
}
@media (max-width: 900px) {
.game-container {
grid-template-columns: 1fr;
}
.stats-panel, .right-panel {
display: none;
}
}
</style>
</head>
<body>
<div class="game-container">
<!-- Left Panel - Stats -->
<div class="stats-panel">
<div class="logo">DRUMMASTER PRO</div>
<div class="score-container">
<div class="score-label">Score</div>
<div class="score-value" id="scoreDisplay">0</div>
</div>
<div class="multiplier-container">
<div class="multiplier-label">Multiplier</div>
<div class="multiplier-value" id="multiplierDisplay">1x</div>
<div class="multiplier-bar">
<div class="multiplier-fill" id="multiplierFill" style="width: 0%"></div>
</div>
</div>
<div class="combo-container">
<div class="combo-value" id="comboDisplay">0</div>
<div class="combo-label">COMBO</div>
</div>
<div class="star-power-container">
<div class="star-power-label">STAR POWER</div>
<div class="star-power-bar">
<div class="star-power-fill" id="starPowerFill" style="width: 0%"></div>
<div class="star-power-text" id="starPowerText">0%</div>
</div>
</div>
<div class="accuracy-container">
<div class="score-label">Accuracy</div>
<div class="accuracy-value good" id="accuracyDisplay">100%</div>
</div>
<div class="midi-status">
<div class="midi-dot" id="midiDot"></div>
<div class="midi-text" id="midiText">No MIDI Connected</div>
</div>
</div>
<!-- Center Panel - Highway -->
<div class="highway-panel">
<div class="stage-background"></div>
<div class="stage-lights">
<div class="stage-light"></div>
<div class="stage-light"></div>
<div class="stage-light"></div>
<div class="stage-light"></div>
</div>
<canvas class="highway-canvas" id="highwayCanvas"></canvas>
<div class="crowd-meter">
<div class="crowd-label">CROWD</div>
<div class="crowd-bar">
<div class="crowd-fill good" id="crowdFill" style="width: 50%"></div>
</div>
</div>
<div class="song-info">
<div class="song-title" id="songTitle">Basic Rock Beat</div>
<div class="song-artist" id="songArtist">Lesson 1</div>
</div>
<div class="streak-flames" id="streakFlames">
<div class="flame" style="height: 60px;"></div>
<div class="flame" style="height: 80px;"></div>
<div class="flame" style="height: 100px;"></div>
<div class="flame" style="height: 80px;"></div>
<div class="flame" style="height: 60px;"></div>
</div>
<div class="hit-zone" id="hitZone">
<div class="hit-pad" data-drum="kick"><span>KICK</span><span class="key-hint">SPACE</span></div>
<div class="hit-pad" data-drum="snare"><span>SNARE</span><span class="key-hint">S</span></div>
<div class="hit-pad" data-drum="hihat"><span>HI-HAT</span><span class="key-hint">H</span></div>
<div class="hit-pad" data-drum="crash"><span>CRASH</span><span class="key-hint">C</span></div>
<div class="hit-pad" data-drum="tom1"><span>TOM 1</span><span class="key-hint">1</span></div>
<div class="hit-pad" data-drum="tom2"><span>TOM 2</span><span class="key-hint">2</span></div>
<div class="hit-pad" data-drum="ride"><span>RIDE</span><span class="key-hint">R</span></div>
</div>
<div class="effects-container" id="effectsContainer"></div>
<div class="controls-bar">
<button class="control-btn" id="playBtn" onclick="togglePlay()">PLAY</button>
<button class="control-btn" onclick="restartSong()">RESTART</button>
<div class="bpm-display"><span id="bpmValue">120</span> BPM</div>
<button class="control-btn" onclick="activateStarPower()" id="starPowerBtn">STAR POWER (TAB)</button>
</div>
</div>
<!-- Right Panel -->
<div class="right-panel">
<div class="lesson-selector">
<div class="lesson-label">Select Lesson</div>
<div class="lesson-grid" id="lessonGrid">
<!-- Generated by JS -->
</div>
</div>
<div class="tempo-control">
<div class="tempo-label">TEMPO</div>
<input type="range" class="tempo-slider" id="tempoSlider" min="40" max="200" value="120">
<div class="tempo-value"><span id="tempoValue">120</span> BPM</div>
</div>
<div class="practice-mode">
<span class="practice-label">Practice Mode (No Fail)</span>
<div class="toggle-switch" id="practiceModeToggle" onclick="togglePracticeMode()"></div>
</div>
<!-- SEFERIM Panel -->
<div class="seferim-panel">
<div class="seferim-header">
<span class="seferim-title">SEFERIM AGI</span>
<span class="seferim-version">G16 v3.1.1</span>
</div>
<div class="seferim-stats">
<div class="seferim-stat">
<div class="seferim-stat-value" id="seferimPhi">0.00</div>
<div class="seferim-stat-label">Global Φ</div>
</div>
<div class="seferim-stat">
<div class="seferim-stat-value" id="seferimK">0</div>
<div class="seferim-stat-label">Knowledge</div>
</div>
<div class="seferim-stat">
<div class="seferim-stat-value" id="seferimTick">0</div>
<div class="seferim-stat-label">Ticks</div>
</div>
<div class="seferim-stat">
<div class="seferim-stat-value" id="seferimLevel">1</div>
<div class="seferim-stat-label">Level</div>
</div>
</div>
<div class="families-mini" id="familiesMini">
<!-- Generated by JS -->
</div>
</div>
</div>
</div>
<!-- Countdown Overlay -->
<div class="countdown-overlay" id="countdownOverlay">
<div class="countdown-number" id="countdownNumber">3</div>
</div>
<!-- Game Over Overlay -->
<div class="game-over-overlay" id="gameOverOverlay">
<div class="results-card">
<div class="results-title">SONG COMPLETE!</div>
<div class="star-rating" id="starRating">
<span class="star">★</span>
<span class="star">★</span>
<span class="star">★</span>
<span class="star">★</span>
<span class="star">★</span>
</div>
<div class="results-score" id="finalScore">0</div>
<div class="results-stats">
<div class="result-stat">
<div class="result-stat-value" id="finalAccuracy">0%</div>
<div class="result-stat-label">Accuracy</div>
</div>
<div class="result-stat">
<div class="result-stat-value" id="finalCombo">0</div>
<div class="result-stat-label">Max Combo</div>
</div>
<div class="result-stat">
<div class="result-stat-value" id="finalStreak">0</div>
<div class="result-stat-label">Note Streak</div>
</div>
</div>
<button class="control-btn" onclick="closeResults()">CONTINUE</button>
</div>
</div>
<script>
// ═══════════════════════════════════════════════════════════════════════════════
// ROCK BAND STYLE DRUM GAME ENGINE
// ═══════════════════════════════════════════════════════════════════════════════
// Game State
const gameState = {
isPlaying: false,
isPracticeMode: false,
score: 0,
combo: 0,
maxCombo: 0,
multiplier: 1,
multiplierProgress: 0,
starPower: 0,
starPowerActive: false,
accuracy: 100,
totalNotes: 0,
hitNotes: 0,
perfectNotes: 0,
greatNotes: 0,
goodNotes: 0,
missedNotes: 0,
crowdMeter: 50,
bpm: 120,
currentLesson: 0,
noteSpeed: 400, // pixels per second
songPosition: 0,
lastBeatTime: 0
};
// Lessons Data
const lessons = [
{
name: "Basic Rock",
artist: "Lesson 1",
difficulty: 1,
bpm: 100,
pattern: {
kick: "1---1---1---1---",
snare: "----1-------1---",
hihat: "1-1-1-1-1-1-1-1-"
}
},
{
name: "Disco Beat",
artist: "Lesson 2",
difficulty: 2,
bpm: 110,
pattern: {
kick: "1---1---1---1---",
snare: "----1-------1---",
hihat: "1111111111111111"
}
},
{
name: "Funk Groove",
artist: "Lesson 3",
difficulty: 3,
bpm: 95,
pattern: {
kick: "1--1--1---1-1---",
snare: "----1--1----1---",
hihat: "1-1-1-1-1-1-1-1-"
}
},
{
name: "Metal Drive",
artist: "Lesson 4",
difficulty: 4,
bpm: 140,
pattern: {
kick: "1-1-1-1-1-1-1-1-",
snare: "----1-------1---",
hihat: "1111111111111111"
}
},
{
name: "Jazz Swing",
artist: "Lesson 5",
difficulty: 3,
bpm: 130,
pattern: {
kick: "1-------1-------",
snare: "----1-------1---",
ride: "1--1--1-1--1--1-"
}
},
{
name: "Drum Fill",
artist: "Lesson 6",
difficulty: 4,
bpm: 100,
pattern: {
kick: "1-------1-------",
snare: "----1-----------",
tom1: "--------1-1-----",
tom2: "------------1-1-"
}
},
{
name: "Double Bass",
artist: "Lesson 7",
difficulty: 5,
bpm: 160,
pattern: {
kick: "1111111111111111",
snare: "----1-------1---",
hihat: "1---1---1---1---"
}
},
{
name: "Polyrhythm",
artist: "Lesson 8",
difficulty: 5,
bpm: 90,
pattern: {
kick: "1--1--1--1--1--1",
snare: "----1-------1---",
hihat: "1-1-1-1-1-1-1-1-",
ride: "--1---1---1---1-"
}
}
];
// Note colors
const drumColors = {
kick: '#ff3366',
snare: '#ffcc00',
hihat: '#00ccff',
crash: '#ff9900',
tom1: '#cc33ff',
tom2: '#33ff66',
tom3: '#ff6633',
ride: '#3366ff'
};
// Key mappings
const keyMap = {
' ': 'kick',
's': 'snare',
'h': 'hihat',
'c': 'crash',
'1': 'tom1',
'2': 'tom2',
'3': 'tom3',
'r': 'ride'
};
// MIDI note mappings
const midiMap = {
36: 'kick',
38: 'snare',
42: 'hihat',
46: 'hihat',
49: 'crash',
48: 'tom1',
45: 'tom2',
43: 'tom3',
51: 'ride'
};
// Notes on highway
let activeNotes = [];
let canvas, ctx;
let animationId;
let lastTime = 0;
// Lane positions
const lanes = ['kick', 'snare', 'hihat', 'crash', 'tom1', 'tom2', 'ride'];
let lanePositions = {};
// ═══════════════════════════════════════════════════════════════════════════════
// INITIALIZATION
// ═══════════════════════════════════════════════════════════════════════════════
document.addEventListener('DOMContentLoaded', () => {
initCanvas();
initLessons();
initMIDI();
initSeferim();
selectLesson(0);
window.addEventListener('resize', initCanvas);
});
function initCanvas() {
canvas = document.getElementById('highwayCanvas');
ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth * 2;
canvas.height = canvas.offsetHeight * 2;
ctx.scale(2, 2);
// Calculate lane positions based on hit pads
const hitZone = document.getElementById('hitZone');
const pads = hitZone.querySelectorAll('.hit-pad');
const containerRect = canvas.getBoundingClientRect();
pads.forEach((pad, index) => {
const padRect = pad.getBoundingClientRect();
const centerX = (padRect.left + padRect.width / 2) - containerRect.left;
lanePositions[pad.dataset.drum] = centerX;
});
}
function initLessons() {
const grid = document.getElementById('lessonGrid');
grid.innerHTML = '';
lessons.forEach((lesson, index) => {
const btn = document.createElement('button');
btn.className = 'lesson-btn';
btn.onclick = () => selectLesson(index);
btn.innerHTML = `
<div>${lesson.name}</div>
<div class="difficulty">
${Array(5).fill(0).map((_, i) =>
`<span class="${i < lesson.difficulty ? 'filled' : ''}"></span>`
).join('')}
</div>
`;
grid.appendChild(btn);
});
}
function selectLesson(index) {
gameState.currentLesson = index;
const lesson = lessons[index];
document.getElementById('songTitle').textContent = lesson.name;
document.getElementById('songArtist').textContent = lesson.artist;
document.getElementById('bpmValue').textContent = lesson.bpm;
gameState.bpm = lesson.bpm;
document.getElementById('tempoSlider').value = lesson.bpm;
document.getElementById('tempoValue').textContent = lesson.bpm;
// Update active lesson button
document.querySelectorAll('.lesson-btn').forEach((btn, i) => {
btn.classList.toggle('active', i === index);
});
// Stop current game and reset
if (gameState.isPlaying) {
stopGame();
}
resetGame();
}
// ═══════════════════════════════════════════════════════════════════════════════
// MIDI HANDLING
// ═══════════════════════════════════════════════════════════════════════════════
function initMIDI() {
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess().then(onMIDISuccess, onMIDIFailure);
} else {
console.log('WebMIDI not supported');
}
}
function onMIDISuccess(midiAccess) {
for (let input of midiAccess.inputs.values()) {
input.onmidimessage = handleMIDIMessage;
}
midiAccess.onstatechange = (e) => {
if (e.port.state === 'connected' && e.port.type === 'input') {
e.port.onmidimessage = handleMIDIMessage;
updateMIDIStatus(true, e.port.name);
}
};
if (midiAccess.inputs.size > 0) {
const firstInput = midiAccess.inputs.values().next().value;
updateMIDIStatus(true, firstInput.name);
}
}
function onMIDIFailure() {
console.log('Could not access MIDI devices');
}
function handleMIDIMessage(message) {
const [status, note, velocity] = message.data;
if (status >= 144 && status <= 159 && velocity > 0) {
const drumType = midiMap[note];
if (drumType) {
handleDrumHit(drumType, velocity);
}
}
}
function updateMIDIStatus(connected, name) {
const dot = document.getElementById('midiDot');
const text = document.getElementById('midiText');
dot.classList.toggle('connected', connected);
text.textContent = connected ? name : 'No MIDI Connected';
}
// ═══════════════════════════════════════════════════════════════════════════════
// KEYBOARD HANDLING
// ═══════════════════════════════════════════════════════════════════════════════
document.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
if (key === 'tab') {
e.preventDefault();
activateStarPower();
return;
}
if (key === 'enter' || key === 'p') {
e.preventDefault();
togglePlay();
return;
}
const drumType = keyMap[key];
if (drumType) {
e.preventDefault();
handleDrumHit(drumType, 100);
}
});
// ═══════════════════════════════════════════════════════════════════════════════
// DRUM HIT HANDLING
// ═══════════════════════════════════════════════════════════════════════════════
function handleDrumHit(drumType, velocity) {
// Visual feedback on pad
const pad = document.querySelector(`.hit-pad[data-drum="${drumType}"]`);
if (pad) {
pad.classList.add('hit');
setTimeout(() => pad.classList.remove('hit'), 100);
}
// Check for note hit
const hitResult = checkNoteHit(drumType);
// Create hit effect
createHitEffect(drumType, hitResult);
// Process through SEFERIM
if (window.SEFERIM) {
window.SEFERIM.processDrumHit(
drumType, velocity, performance.now(), performance.now(),
lessons[gameState.currentLesson].name
);
updateSeferimUI();
}
updateUI();
}
function checkNoteHit(drumType) {
const hitWindow = 100; // ms
const perfectWindow = 30;
const greatWindow = 60;
const lane = lanePositions[drumType];
const hitY = canvas.offsetHeight - 110;
let closestNote = null;
let closestDist = Infinity;
for (let i = activeNotes.length - 1; i >= 0; i--) {
const note = activeNotes[i];
if (note.drum === drumType && !note.hit) {
const dist = Math.abs(note.y - hitY);
if (dist < closestDist) {
closestDist = dist;
closestNote = note;
}
}
}
if (closestNote && closestDist < hitWindow) {
closestNote.hit = true;
let judgment, points;
if (closestDist < perfectWindow) {
judgment = 'perfect';