Compare commits
1 Commits
fix/132-re
...
sprint/iss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89713dc867 |
@@ -1 +0,0 @@
|
||||
# Trivial file to re-trigger CI after stale run
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
25
index.html
25
index.html
@@ -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>
|
||||
|
||||
60
js/data.js
60
js/data.js
@@ -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',
|
||||
|
||||
33
js/engine.js
33
js/engine.js
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user