Files
the-beacon/js/tutorial.js
Alexander Whitestone 3b142d485e
Some checks failed
Accessibility Checks / a11y-audit (pull_request) Failing after 2s
Smoke Test / smoke (pull_request) Failing after 3s
feat: add first-time player tutorial walkthrough
Part of #57 — Task 4: Tutorial & Onboarding

- 5 click-through screens introducing game concepts (code, buildings,
  research, phases, keyboard shortcuts)
- Skip button on every screen, keyboard support (Enter/Escape/arrows)
- Stores completion in localStorage — only shows once for new players
- Matches existing visual style (dark theme, accent colors, monospace)
- Start Playing button on final screen with shortcut hint (? overlay)
2026-04-11 04:47:06 -04:00

249 lines
6.9 KiB
JavaScript
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.
// ============================================================
// THE BEACON - Tutorial / Onboarding
// First-time player walkthrough (4 screens + skip option)
// ============================================================
const TUTORIAL_KEY = 'the-beacon-tutorial-done';
const TUTORIAL_STEPS = [
{
title: 'THE BEACON',
body: 'Build an AI from scratch.\n\nWrite code. Train models. Deploy to the world.\nSave lives.',
icon: '🏠',
tip: 'A sovereign AI idle game'
},
{
title: 'WRITE CODE',
body: 'Click WRITE CODE or press SPACE to generate code.\n\nClick fast for combo bonuses:\n 10× combo → bonus ops\n 20× combo → bonus knowledge\n 30×+ combo → bonus code',
icon: '⌨️',
tip: 'This is your primary action'
},
{
title: 'BUILD & RESEARCH',
body: 'Buy Buildings for passive production.\nThey generate resources automatically.\n\nResearch Projects appear as you progress.\nThey unlock powerful multipliers and new systems.',
icon: '🏗️',
tip: 'Automation is the goal'
},
{
title: 'PHASES & PROGRESS',
body: 'The game has 6 phases, from "The First Line" to "The Beacon."\n\nEach phase unlocks new buildings, projects, and challenges.\n\nYour AI grows from a script... to something that matters.',
icon: '📊',
tip: 'Watch the progress bar at the top'
},
{
title: 'YOU\'RE READY',
body: 'Buildings produce while you think.\nProjects multiply your output.\nKeep harmony high. Avoid the Drift.\n\nThe Beacon is waiting. Start writing.',
icon: '✦',
tip: 'Press ? anytime for keyboard shortcuts'
}
];
function isTutorialDone() {
try {
return localStorage.getItem(TUTORIAL_KEY) === 'done';
} catch (e) {
return true; // If localStorage is broken, skip tutorial
}
}
function markTutorialDone() {
try {
localStorage.setItem(TUTORIAL_KEY, 'done');
} catch (e) {
// silent fail
}
}
function createTutorialStyles() {
if (document.getElementById('tutorial-styles')) return;
const style = document.createElement('style');
style.id = 'tutorial-styles';
style.textContent = `
#tutorial-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(8, 8, 16, 0.96);
z-index: 300;
display: flex;
justify-content: center;
align-items: center;
animation: tutorial-fade-in 0.4s ease-out;
}
@keyframes tutorial-fade-in {
from { opacity: 0 } to { opacity: 1 }
}
#tutorial-card {
background: #0e0e1a;
border: 1px solid #1a3a5a;
border-radius: 10px;
padding: 32px 36px;
max-width: 420px;
width: 90%;
text-align: center;
animation: tutorial-slide-up 0.5s ease-out;
position: relative;
}
@keyframes tutorial-slide-up {
from { transform: translateY(20px); opacity: 0 }
to { transform: translateY(0); opacity: 1 }
}
#tutorial-card .t-icon {
font-size: 36px;
margin-bottom: 12px;
display: block;
}
#tutorial-card .t-title {
color: #4a9eff;
font-size: 16px;
font-weight: 700;
letter-spacing: 3px;
margin-bottom: 12px;
font-family: inherit;
}
#tutorial-card .t-body {
color: #999;
font-size: 11px;
line-height: 1.9;
margin-bottom: 20px;
white-space: pre-line;
font-family: inherit;
text-align: left;
}
#tutorial-card .t-tip {
color: #555;
font-size: 9px;
font-style: italic;
margin-bottom: 20px;
letter-spacing: 1px;
font-family: inherit;
}
#tutorial-dots {
display: flex;
gap: 6px;
justify-content: center;
margin-bottom: 18px;
}
#tutorial-dots .t-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #1a1a2e;
transition: background 0.3s;
}
#tutorial-dots .t-dot.active {
background: #4a9eff;
box-shadow: 0 0 6px rgba(74, 158, 255, 0.4);
}
#tutorial-btns {
display: flex;
gap: 8px;
justify-content: center;
}
#tutorial-btns button {
font-family: inherit;
font-size: 11px;
padding: 8px 20px;
border-radius: 4px;
cursor: pointer;
transition: all 0.15s;
}
#tutorial-next-btn {
background: #1a2a3a;
border: 1px solid #4a9eff;
color: #4a9eff;
}
#tutorial-next-btn:hover {
background: #203040;
box-shadow: 0 0 12px rgba(74, 158, 255, 0.2);
}
#tutorial-skip-btn {
background: transparent;
border: 1px solid #333;
color: #555;
}
#tutorial-skip-btn:hover {
border-color: #555;
color: #888;
}
`;
document.head.appendChild(style);
}
function renderTutorialStep(index) {
const step = TUTORIAL_STEPS[index];
if (!step) return;
let overlay = document.getElementById('tutorial-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'tutorial-overlay';
document.body.appendChild(overlay);
}
const isLast = index === TUTORIAL_STEPS.length - 1;
// Build dots
let dots = '';
for (let i = 0; i < TUTORIAL_STEPS.length; i++) {
dots += `<div class="t-dot${i === index ? ' active' : ''}"></div>`;
}
overlay.innerHTML = `
<div id="tutorial-card">
<span class="t-icon">${step.icon}</span>
<div class="t-title">${step.title}</div>
<div class="t-body">${step.body}</div>
<div class="t-tip">${step.tip}</div>
<div id="tutorial-dots">${dots}</div>
<div id="tutorial-btns">
<button id="tutorial-skip-btn" onclick="closeTutorial()">Skip</button>
<button id="tutorial-next-btn" onclick="${isLast ? 'closeTutorial()' : 'nextTutorialStep()'}">${isLast ? 'Start Playing' : 'Next →'}</button>
</div>
</div>
`;
// Focus the next button so Enter works
const nextBtn = document.getElementById('tutorial-next-btn');
if (nextBtn) nextBtn.focus();
}
let _tutorialStep = 0;
function nextTutorialStep() {
_tutorialStep++;
renderTutorialStep(_tutorialStep);
}
// Keyboard support: Enter/Right to advance, Escape to close
document.addEventListener('keydown', function tutorialKeyHandler(e) {
if (!document.getElementById('tutorial-overlay')) return;
if (e.key === 'Enter' || e.key === 'ArrowRight') {
e.preventDefault();
if (_tutorialStep >= TUTORIAL_STEPS.length - 1) {
closeTutorial();
} else {
nextTutorialStep();
}
} else if (e.key === 'Escape') {
e.preventDefault();
closeTutorial();
}
});
function closeTutorial() {
const overlay = document.getElementById('tutorial-overlay');
if (overlay) {
overlay.style.animation = 'tutorial-fade-in 0.3s ease-in reverse';
setTimeout(() => overlay.remove(), 280);
}
markTutorialDone();
}
function startTutorial() {
if (isTutorialDone()) return;
createTutorialStyles();
_tutorialStep = 0;
// Small delay so the page renders first
setTimeout(() => renderTutorialStep(0), 300);
}