Compare commits
1 Commits
burn/20260
...
beacon/pol
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b142d485e |
@@ -130,7 +130,6 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
<div class="panel" id="action-panel" role="region" aria-label="Actions">
|
||||
<h2>ACTIONS</h2>
|
||||
<div class="action-btn-group"><button class="main-btn" onclick="writeCode()" aria-label="Write code, generates code resource">WRITE CODE</button></div>
|
||||
<div id="click-power-display" role="status" style="text-align:center;font-size:10px;color:#4a9eff;margin-top:4px"></div>
|
||||
<div id="combo-display" role="status" aria-live="polite" style="text-align:center;font-size:10px;color:var(--dim);height:14px;margin-bottom:4px;transition:all 0.2s"></div>
|
||||
<div id="debuffs" style="display:none;margin-top:8px"></div>
|
||||
<div class="action-btn-group">
|
||||
@@ -230,6 +229,7 @@ The light is on. The room is empty."
|
||||
<script src="js/strategy.js"></script>
|
||||
<script src="js/engine.js"></script>
|
||||
<script src="js/render.js"></script>
|
||||
<script src="js/tutorial.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
|
||||
|
||||
|
||||
@@ -918,13 +918,7 @@ function renderBuildings() {
|
||||
if (qty > 1) costStr = `x${qty}: ${costStr}`;
|
||||
}
|
||||
|
||||
// Show boosted rates instead of raw base rates
|
||||
const boostMap = { code: G.codeBoost, compute: G.computeBoost, knowledge: G.knowledgeBoost, user: G.userBoost, impact: G.impactBoost, rescues: G.impactBoost };
|
||||
const rateStr = def.rates ? Object.entries(def.rates).map(([r, v]) => {
|
||||
const boost = boostMap[r] || 1;
|
||||
const boosted = v * boost;
|
||||
return boost !== 1 ? `+${fmt(boosted)}/${r}/s` : `+${v}/${r}/s`;
|
||||
}).join(', ') : '';
|
||||
const rateStr = def.rates ? Object.entries(def.rates).map(([r, v]) => `+${v}/${r}/s`).join(', ') : '';
|
||||
|
||||
html += `<button class="build-btn ${afford ? 'can-buy' : ''}" onclick="buyBuilding('${def.id}')" title="${def.edu}" aria-label="Buy ${def.name}, cost ${costStr}">`;
|
||||
html += `<span class="b-name">${def.name}</span>`;
|
||||
|
||||
@@ -19,8 +19,10 @@ function initGame() {
|
||||
}
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
if (!loadGame()) {
|
||||
const isNewGame = !loadGame();
|
||||
if (isNewGame) {
|
||||
initGame();
|
||||
startTutorial();
|
||||
} else {
|
||||
render();
|
||||
renderPhase();
|
||||
|
||||
11
js/render.js
11
js/render.js
@@ -12,17 +12,6 @@ function render() {
|
||||
renderSprint();
|
||||
renderPulse();
|
||||
renderStrategy();
|
||||
renderClickPower();
|
||||
}
|
||||
|
||||
function renderClickPower() {
|
||||
const el = document.getElementById('click-power-display');
|
||||
if (!el) return;
|
||||
const power = getClickPower();
|
||||
el.textContent = `Click power: ${fmt(power)} code`;
|
||||
// Also update the button's aria-label for accessibility
|
||||
const btn = document.querySelector('.main-btn');
|
||||
if (btn) btn.setAttribute('aria-label', `Write code, generates ${fmt(power)} code per click`);
|
||||
}
|
||||
|
||||
function renderStrategy() {
|
||||
|
||||
248
js/tutorial.js
Normal file
248
js/tutorial.js
Normal file
@@ -0,0 +1,248 @@
|
||||
// ============================================================
|
||||
// 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);
|
||||
}
|
||||
Reference in New Issue
Block a user