Files
the-beacon/js/main.js
Timmy ecee3174a3
All checks were successful
CI / test Auto-passed by Timmy review
CI / validate Auto-passed by Timmy review
Smoke Test / smoke Auto-passed by Timmy review
Review Approval Gate / verify-review Auto-passed by Timmy review
Smoke Test / smoke (pull_request) Auto-passed by Timmy review cron job
Accessibility Checks / a11y-audit (pull_request) Auto-passed by Timmy review cron job
feat: custom tooltip system for buildings and projects (#57)
Replace native browser title= tooltips with styled custom tooltips
that match the game's dark theme. Tooltips appear instantly on hover
with building/project name and educational content.

- Add CSS for #custom-tooltip with dark theme styling
- Add tooltip div to HTML body
- Add event delegation in main.js for [data-edu] elements
- Convert renderBuildings and renderProjects to use data-edu
  and data-tooltip-label attrs instead of title=
- Tooltip follows cursor with screen-edge clamping

Refs: Epic #57 — Night of Polish, Task 4 (Tooltip system)
2026-04-12 00:44:43 -04:00

152 lines
4.9 KiB
JavaScript

// === INITIALIZATION ===
function initGame() {
G.startedAt = Date.now();
G.startTime = Date.now();
G.phase = 1;
G.deployFlag = 0;
G.sovereignFlag = 0;
G.beaconFlag = 0;
updateRates();
render();
renderPhase();
log('The screen is blank. Write your first line of code.', true);
log('Click WRITE CODE or press SPACE to start.');
log('Build AutoCode for passive production.');
log('Watch for Research Projects to appear.');
log('Keys: SPACE=Code S=Sprint 1-4=Ops B=Buy x1/10/MAX E=Export I=Import Ctrl+S=Save ?=Help');
log('Tip: Click fast for combo bonuses! 10x=ops, 20x=knowledge, 30x+=bonus code.');
}
window.addEventListener('load', function () {
const isNewGame = !loadGame();
if (isNewGame) {
initGame();
startTutorial();
} else {
render();
renderPhase();
if (G.driftEnding) {
G.running = false;
renderDriftEnding();
} else if (G.beaconEnding) {
G.running = false;
renderBeaconEnding();
} else {
log('Game loaded. Welcome back to The Beacon.');
}
}
// Game loop at 10Hz (100ms tick)
setInterval(tick, 100);
// Auto-save every 30 seconds
setInterval(saveGame, CONFIG.AUTO_SAVE_INTERVAL);
// Update education every 10 seconds
setInterval(updateEducation, 10000);
});
// Help overlay
function toggleHelp() {
const el = document.getElementById('help-overlay');
if (!el) return;
const isOpen = el.style.display === 'flex';
el.style.display = isOpen ? 'none' : 'flex';
}
// Keyboard shortcuts
window.addEventListener('keydown', function (e) {
// Help toggle (? or /) — works even in input fields
if (e.key === '?' || e.key === '/') {
// Only trigger ? when not typing in an input
if (e.target === document.body || e.key === '?') {
if (e.key === '?' || (e.key === '/' && e.target === document.body)) {
e.preventDefault();
toggleHelp();
return;
}
}
}
if (e.code === 'Space' && e.target === document.body) {
e.preventDefault();
writeCode();
}
if (e.target !== document.body) return;
if (e.code === 'Digit1') doOps('boost_code');
if (e.code === 'Digit2') doOps('boost_compute');
if (e.code === 'Digit3') doOps('boost_knowledge');
if (e.code === 'Digit4') doOps('boost_trust');
if (e.code === 'KeyB') {
// Cycle: 1 -> 10 -> MAX -> 1
if (G.buyAmount === 1) setBuyAmount(10);
else if (G.buyAmount === 10) setBuyAmount(-1);
else setBuyAmount(1);
}
if (e.code === 'KeyS') activateSprint();
if (e.code === 'KeyE') exportSave();
if (e.code === 'KeyI') importSave();
if (e.code === 'Escape') {
const el = document.getElementById('help-overlay');
if (el && el.style.display === 'flex') toggleHelp();
}
});
// Ctrl+S to save (must be on keydown to preventDefault)
window.addEventListener('keydown', function (e) {
if ((e.ctrlKey || e.metaKey) && e.code === 'KeyS') {
e.preventDefault();
saveGame();
}
});
// Save-on-pause: auto-save when tab is hidden or closed (#57 Mobile Polish)
document.addEventListener('visibilitychange', function () {
if (document.hidden) {
saveGame();
}
});
window.addEventListener('beforeunload', function () {
saveGame();
});
// === CUSTOM TOOLTIP SYSTEM (#57) ===
// Replaces native title= tooltips with styled, instant-appearing tooltips.
// Elements opt in via data-edu="..." and data-tooltip-label="..." attributes.
(function () {
const tip = document.getElementById('custom-tooltip');
if (!tip) return;
document.addEventListener('mouseover', function (e) {
const el = e.target.closest('[data-edu]');
if (!el) return;
const label = el.getAttribute('data-tooltip-label') || '';
const edu = el.getAttribute('data-edu') || '';
let html = '';
if (label) html += '<div class="tt-label">' + label + '</div>';
if (edu) html += '<div class="tt-edu">' + edu + '</div>';
if (!html) return;
tip.innerHTML = html;
tip.classList.add('visible');
});
document.addEventListener('mouseout', function (e) {
const el = e.target.closest('[data-edu]');
if (el) tip.classList.remove('visible');
});
document.addEventListener('mousemove', function (e) {
if (!tip.classList.contains('visible')) return;
const pad = 12;
let x = e.clientX + pad;
let y = e.clientY + pad;
// Keep tooltip on screen
const tw = tip.offsetWidth;
const th = tip.offsetHeight;
if (x + tw > window.innerWidth - 8) x = e.clientX - tw - pad;
if (y + th > window.innerHeight - 8) y = e.clientY - th - pad;
tip.style.left = x + 'px';
tip.style.top = y + 'px';
});
})();