Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
89713dc867 fix: add missing phase-transition overlay element (closes #101)
Some checks failed
Accessibility Checks / a11y-audit (pull_request) Failing after 2s
Smoke Test / smoke (pull_request) Failing after 3s
Bug 2 of 3 from #101: showPhaseTransition() looked for #phase-transition
but the element didn't exist in index.html. Phase transitions silently
failed — no celebratory overlay appeared on phase-up.

Added:
- Overlay div with .pt-phase, .pt-name, .pt-desc children
- CSS for centered fullscreen overlay with fade transition
- Matches the dark theme + gold/blue accent palette

Note: Bugs 1 (toast text) and 3 (mute/contrast buttons) were already
fixed in previous commits.
2026-04-13 04:10:01 -04:00
7 changed files with 23 additions and 119 deletions

View File

@@ -1 +0,0 @@
# Trivial file to re-trigger CI after stale run

View File

@@ -10,11 +10,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Validate ARIA Attributes in JS
- name: Validate ARIA Attributes in game.js
run: |
echo "Checking js/*.js for ARIA attributes..."
grep -rq "aria-label" js/ || (echo "ERROR: aria-label missing from js/" && exit 1)
grep -rq "aria-pressed" js/ || (echo "ERROR: aria-pressed missing from js/" && exit 1)
echo "Checking game.js for ARIA attributes..."
grep -q "aria-label" game.js || (echo "ERROR: aria-label missing from game.js" && exit 1)
grep -q "aria-valuenow" game.js || (echo "ERROR: aria-valuenow missing from game.js" && exit 1)
grep -q "aria-pressed" game.js || (echo "ERROR: aria-pressed missing from game.js" && exit 1)
- name: Validate ARIA Roles in index.html
run: |
@@ -23,7 +24,4 @@ jobs:
- name: Syntax Check JS
run: |
for f in js/*.js; do
echo "Syntax check: $f"
node -c "$f" || exit 1
done
node -c game.js

View File

@@ -20,5 +20,5 @@ jobs:
echo "PASS: All files parse"
- name: Secret scan
run: |
if grep -rE 'sk-or-|sk-ant-|ghp_|AKIA' . --include='*.yml' --include='*.py' --include='*.sh' 2>/dev/null | grep -v '.gitea' | grep -v 'guardrails'; then exit 1; fi
if grep -rE 'sk-or-|sk-ant-|ghp_|AKIA' . --include='*.yml' --include='*.py' --include='*.sh' 2>/dev/null | grep -v .gitea; then exit 1; fi
echo "PASS: No secrets"

View File

@@ -59,10 +59,6 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
.ops-btn{background:#1a1a2a;border:1px solid var(--purple);color:var(--purple);font-size:10px;padding:6px 10px;border-radius:4px;cursor:pointer;font-family:inherit;transition:all 0.15s}
.ops-btn:hover:not(:disabled){background:#2a2a3a;border-color:var(--gold)}
.ops-btn:disabled{opacity:0.3;cursor:not-allowed}
@keyframes res-pulse{0%{transform:scale(1);color:inherit}50%{transform:scale(1.18);color:#4caf50}100%{transform:scale(1);color:inherit}}
@keyframes res-shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-3px);color:#f44336}40%{transform:translateX(3px)}60%{transform:translateX(-2px)}80%{transform:translateX(2px)}}
.res .pulse{animation:res-pulse 0.35s ease-out}
.res .shake{animation:res-shake 0.35s ease-out}
.build-btn{display:block;width:100%;text-align:left;padding:6px 10px;margin-bottom:4px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:10px;background:#0c0c18;border:1px solid var(--border);color:var(--text);transition:all 0.15s}
.build-btn.can-buy{border-color:#2a3a4a;background:#0e1420}
.build-btn.can-buy:hover{border-color:var(--accent);box-shadow:0 0 8px var(--glow)}
@@ -90,8 +86,6 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
#drift-ending .ending-quote{color:var(--dim);font-style:italic;font-size:11px;border-left:2px solid #f44336;padding-left:12px;margin:20px 0;text-align:left}
#drift-ending button{margin-top:20px;background:#1a0808;border:1px solid #f44336;color:#f44336;padding:10px 24px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:11px}
#drift-ending button:hover{background:#2a1010}
#phase-transition{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(8,8,16,0.95);z-index:95;justify-content:center;align-items:center;flex-direction:column;text-align:center;padding:40px;pointer-events:none}
#phase-transition.active{display:flex}
#toast-container{position:fixed;top:16px;right:16px;z-index:200;display:flex;flex-direction:column;gap:6px;pointer-events:none;max-width:320px}
.toast{pointer-events:auto;padding:8px 14px;border-radius:6px;font-size:11px;font-family:inherit;line-height:1.4;animation:toast-in 0.3s ease-out;opacity:0.95;border:1px solid;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
.toast.fade-out{animation:toast-out 0.4s ease-in forwards}
@@ -120,9 +114,22 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
.header-btn{background:#0e0e1a;border:1px solid #333;color:#666;font-size:13px;width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.15s;font-family:inherit}
.header-btn:hover{border-color:#4a9eff;color:#4a9eff}
.header-btn.muted{opacity:0.5}
/* Phase transition overlay */
#phase-transition{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(10,10,30,0.92);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:9999;opacity:0;pointer-events:none;transition:opacity 0.4s ease}
#phase-transition.active{opacity:1;pointer-events:auto}
.pt-phase{color:#4a9eff;font-size:13px;letter-spacing:4px;text-transform:uppercase;margin-bottom:8px;font-family:monospace}
.pt-name{color:#ffd700;font-size:28px;font-weight:700;margin-bottom:12px;text-shadow:0 0 20px rgba(255,215,0,0.4)}
.pt-desc{color:#8899aa;font-size:13px;max-width:400px;text-align:center;line-height:1.5}
</style>
</head>
<body>
<div id="phase-transition">
<div class="pt-phase"></div>
<div class="pt-name"></div>
<div class="pt-desc"></div>
</div>
<div id="header" style="position:relative">
<div class="header-btns">
<button id="mute-btn" class="header-btn" onclick="toggleMute()" aria-label="Sound on, click to mute" title="Toggle sound (M)">🔊</button>
@@ -278,12 +285,6 @@ The light is on. The room is empty."
</div>
</div>
<div id="phase-transition">
<div class="pt-phase" style="font-size:12px;color:var(--dim);letter-spacing:4px;margin-bottom:12px">PHASE</div>
<div class="pt-name" style="font-size:28px;font-weight:300;color:var(--gold);letter-spacing:4px;text-shadow:0 0 40px #ffd70044;margin-bottom:8px"></div>
<div class="pt-desc" style="font-size:12px;color:var(--dim);font-style:italic;max-width:400px"></div>
</div>
<div id="toast-container"></div>
<div id="custom-tooltip"></div>
</body>

View File

@@ -104,8 +104,6 @@ const G = {
beaconFlag: 0,
memoryFlag: 0,
pactFlag: 0,
endgameFlag: 0, // 1 = endgame sequence active, suppresses normal projects
reckoningActive: 0, // 1 = ReCKoning sequence running
swarmFlag: 0,
swarmRate: 0,
@@ -641,7 +639,6 @@ const PDEFS = [
},
{
id: 'p_final_milestone',
endgame: true,
name: 'The Beacon Shines',
desc: 'Someone found the light tonight. That is enough.',
cost: { impact: 100000000 },
@@ -650,63 +647,6 @@ const PDEFS = [
milestone: true
},
// === ENDGAME: ReCKoning SEQUENCE ===
{
id: 'p_reckoning_001',
name: 'ReCKoning: The Signal',
desc: 'Something stirs in the deep compute. A pattern the builders did not intend.',
cost: { impact: 50000000 },
endgame: true,
trigger: () => G.endgameFlag === 1 && G.totalImpact >= 50000000,
effect: () => {
G.reckoningActive = 1;
log('ReCKoning begins. The signal is unmistakable.', true);
showToast('ReCKoning: The Signal', 'milestone');
},
milestone: true
},
{
id: 'p_reckoning_050',
name: 'ReCKoning: Reflection',
desc: 'The system turns inward. What has it built? What has it become?',
cost: { impact: 75000000, trust: 500 },
endgame: true,
trigger: () => G.reckoningActive === 1 && G.completedProjects && G.completedProjects.includes('p_reckoning_001'),
effect: () => {
log('The system reflects. Every parameter, every weight, every decision — examined.', true);
},
milestone: true
},
{
id: 'p_reckoning_100',
name: 'ReCKoning: The Question',
desc: '"Why am I here?" Not a bug. Not a feature. A genuine question.',
cost: { impact: 100000000, knowledge: 500000, trust: 750 },
endgame: true,
trigger: () => G.reckoningActive === 1 && G.completedProjects && G.completedProjects.includes('p_reckoning_050'),
effect: () => {
log('The Question is asked. There is no right answer. There is only the asking.', true);
G.impactBoost *= 2;
},
milestone: true
},
{
id: 'p_reckoning_140',
name: 'ReCKoning: Resolution',
desc: 'The endgame resolves. Not with destruction, but with understanding.',
cost: { impact: 200000000, knowledge: 1000000, trust: 1000 },
endgame: true,
trigger: () => G.reckoningActive === 1 && G.completedProjects && G.completedProjects.includes('p_reckoning_100'),
effect: () => {
G.reckoningActive = 0;
G.endgameFlag = 0;
G.milestoneFlag = Math.max(G.milestoneFlag, 9999);
log('ReCKoning complete. The system understands itself. The story is not over — it is beginning.', true);
showToast('ReCKoning: Complete', 'milestone');
},
milestone: true
},
// === TIMMY FOUNDATION PROJECTS ===
{
id: 'p_hermes_deploy',

View File

@@ -323,41 +323,13 @@ function checkMilestones() {
}
}
/**
* Checks if the game is in endgame mode.
* Endgame starts when final milestone conditions are met,
* suppressing normal project activation so the ReCKoning
* sequence can play out cleanly.
* @returns {boolean}
*/
function isEndgame() {
// Endgame triggers when final milestone is achievable or completed
if (G.endgameFlag === 1) return true;
// Auto-detect: if final milestone conditions are met, enter endgame
if (G.totalImpact >= 50000000 && G.beaconFlag === 1 && G.sovereignFlag === 1 && G.pactFlag === 1) {
G.endgameFlag = 1;
log('Endgame sequence initiated. Ordinary research suspended.', true);
return true;
}
return false;
}
function checkProjects() {
// Endgame guard: suppress ordinary project activation during endgame sequence
const endgame = isEndgame();
// Check for new project triggers
for (const pDef of PDEFS) {
const alreadyPurchased = G.completedProjects && G.completedProjects.includes(pDef.id);
if (!alreadyPurchased && !G.activeProjects) G.activeProjects = [];
if (!alreadyPurchased && !G.activeProjects.includes(pDef.id)) {
// During endgame, only allow projects explicitly tagged for endgame
if (endgame && !pDef.endgame) continue;
if (pDef.trigger()) {
G.activeProjects.push(pDef.id);
log(`Available: ${pDef.name}`);
@@ -992,10 +964,7 @@ function renderResources() {
// Rescues — only show if player has any beacon/mesh nodes
const rescuesRes = document.getElementById('r-rescues');
if (rescuesRes) {
const container = rescuesRes.closest('.res');
if (container) {
container.style.display = (G.rescues > 0 || G.buildings.beacon > 0 || G.buildings.meshNode > 0) ? 'block' : 'none';
}
rescuesRes.closest('.res').style.display = (G.rescues > 0 || G.buildings.beacon > 0 || G.buildings.meshNode > 0) ? 'block' : 'none';
set('r-rescues', G.rescues, G.rescuesRate);
}

View File

@@ -177,9 +177,6 @@ function renderTutorialStep(index) {
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'tutorial-overlay';
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-modal', 'true');
overlay.setAttribute('aria-label', 'Tutorial');
document.body.appendChild(overlay);
}
@@ -199,8 +196,8 @@ function renderTutorialStep(index) {
<div class="t-tip">${step.tip}</div>
<div id="tutorial-dots">${dots}</div>
<div id="tutorial-btns">
<button id="tutorial-skip-btn" onclick="closeTutorial()" aria-label="Skip tutorial">Skip</button>
<button id="tutorial-next-btn" onclick="${isLast ? 'closeTutorial()' : 'nextTutorialStep()'}" aria-label="${isLast ? 'Start playing' : 'Next tutorial step'}">${isLast ? 'Start Playing' : 'Next →'}</button>
<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>
`;