Level 01: Paddington Skatepark from the council plan + park guides — heightAt(x,z) single-source terrain, grindable rails/ledges, named gaps, B-O-O-K-Q-U-O-Y letters with the BOOKQUOYORK callback. SKATE / BMX / BLADES (blades cop thrown rubbish, as is tradition). character_kit two-rig banks: physics owns the root, clips are cosmetic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
77 lines
2.8 KiB
JavaScript
77 lines
2.8 KiB
JavaScript
// BOOKQUOY HUD — score, combo, timer, the letters, and the callouts.
|
|
import { comboLabel } from './tricks.js';
|
|
|
|
export class Hud {
|
|
constructor() {
|
|
this.$ = id => document.getElementById(id);
|
|
this.letterOrder = ['B', 'O', 'O', 'K', 'Q', 'U', 'O', 'Y'];
|
|
this.reset();
|
|
}
|
|
reset() {
|
|
this.score = 0; this.combo = []; this.comboScore = 0;
|
|
this.lettersLit = 0;
|
|
this.$('score').textContent = '0';
|
|
this.$('combo').textContent = '';
|
|
this.renderLetters();
|
|
this.$('flash').textContent = '';
|
|
this.$('over').style.display = 'none';
|
|
}
|
|
renderLetters() {
|
|
this.$('letters').innerHTML = this.letterOrder
|
|
.map((c, i) => `<span class="${i < this.lettersLit ? 'lit' : ''}">${c}</span>`).join('');
|
|
}
|
|
addTrick(name, score) {
|
|
this.combo.push({ name, score }); this.comboScore += score;
|
|
this.$('combo').textContent = comboLabel(this.combo) + ` (${this.comboScore * this.combo.length})`;
|
|
}
|
|
addGrindPoints(pts) {
|
|
if (!this.combo.length) this.combo.push({ name: 'Grind', score: 0 });
|
|
this.comboScore += pts;
|
|
this.$('combo').textContent = comboLabel(this.combo) + ` (${Math.round(this.comboScore * this.combo.length)})`;
|
|
}
|
|
bank() {
|
|
if (!this.combo.length) return;
|
|
this.score += Math.round(this.comboScore * this.combo.length);
|
|
this.$('score').textContent = this.score.toLocaleString();
|
|
this.combo = []; this.comboScore = 0;
|
|
this.$('combo').textContent = '';
|
|
}
|
|
dropCombo() {
|
|
if (this.combo.length) this.flash('BAILED — combo lost', '#ff6b6b');
|
|
this.combo = []; this.comboScore = 0;
|
|
this.$('combo').textContent = '';
|
|
}
|
|
letter(ch) {
|
|
this.lettersLit = Math.min(8, this.lettersLit + 1);
|
|
this.renderLetters();
|
|
this.flash(ch + ' !', '#ffd93b');
|
|
return this.lettersLit === 8;
|
|
}
|
|
flash(text, color = '#9be89b') {
|
|
const f = this.$('flash');
|
|
f.textContent = text; f.style.color = color;
|
|
f.style.opacity = '1';
|
|
clearTimeout(this._ft);
|
|
this._ft = setTimeout(() => f.style.opacity = '0', 1400);
|
|
}
|
|
bigFlash(text) {
|
|
const f = this.$('bigflash');
|
|
f.textContent = text; f.style.opacity = '1';
|
|
clearTimeout(this._bt);
|
|
this._bt = setTimeout(() => f.style.opacity = '0', 2600);
|
|
}
|
|
timer(t) {
|
|
const m = Math.floor(Math.max(t, 0) / 60), s = Math.floor(Math.max(t, 0) % 60);
|
|
this.$('timer').textContent = `${m}:${String(s).padStart(2, '0')}`;
|
|
this.$('timer').style.color = t < 15 ? '#ff6b6b' : '#dde';
|
|
}
|
|
sessionOver(score) {
|
|
const best = Math.max(score, +(localStorage.getItem('bookquoy_best') || 0));
|
|
localStorage.setItem('bookquoy_best', best);
|
|
this.$('finalScore').textContent = score.toLocaleString();
|
|
this.$('bestScore').textContent = best.toLocaleString();
|
|
this.$('over').style.display = 'flex';
|
|
}
|
|
title(show) { this.$('title').style.display = show ? 'flex' : 'none'; }
|
|
}
|