Add static file serving and enhance game UI

- Implement StaticFilesController to serve CSS, JS, and asset files for the BreakEscape engine.
- Update routes to include static file paths for CSS, JS, and assets.
- Refactor game show view to load multiple CSS files and include Google Fonts.
- Remove application stylesheet link from the layout.
- Modify various CSS files to improve layout and styling, including HUD and inventory.
- Update JavaScript files to ensure asset paths are correctly prefixed with /break_escape/.
- Enhance minigame UI components, including notifications, modals, and overlays.
- Adjust game-over screen and health UI to use correct asset paths.
- Update constants and crypto workstation utility to reflect new asset paths.
This commit is contained in:
Z. Cliffe Schreuders
2025-11-21 15:27:54 +00:00
parent 0ef0b028b5
commit 5b11fa9dbb
28 changed files with 290 additions and 76 deletions

View File

@@ -0,0 +1,88 @@
module BreakEscape
class StaticFilesController < BreakEscape::ApplicationController
skip_before_action :verify_authenticity_token
def serve
# Use the BreakEscape engine's root, not Rails.root
engine_root = BreakEscape::Engine.root
# Determine the actual file path based on the request URL
request_path = request.path
# Map different URL patterns to their file locations
# Remember: request_path will be /break_escape/css/... when mounted at /break_escape
file_path = case request_path
when %r{^/break_escape/css/}
engine_root.join('public', 'break_escape', 'css', params[:path])
when %r{^/break_escape/js/}
engine_root.join('public', 'break_escape', 'js', params[:path])
when %r{^/break_escape/assets/}
engine_root.join('public', 'break_escape', 'assets', params[:path])
when %r{^/break_escape/stylesheets/}
engine_root.join('public', 'break_escape', 'css', params[:path])
else
# Fallback for any other pattern
engine_root.join('public', 'break_escape', params[:path])
end
# Security: prevent directory traversal
base_path = engine_root.join('public', 'break_escape').to_s
unless file_path.to_s.start_with?(base_path)
return render_not_found
end
unless File.file?(file_path)
return render_not_found
end
# Determine content type
content_type = determine_content_type(file_path.to_s)
send_file file_path, type: content_type, disposition: 'inline'
rescue Errno::ENOENT
render_not_found
end
private
def determine_content_type(file_path)
case File.extname(file_path).downcase
when '.css'
'text/css'
when '.js'
'application/javascript'
when '.json'
'application/json'
when '.png'
'image/png'
when '.jpg', '.jpeg'
'image/jpeg'
when '.gif'
'image/gif'
when '.svg'
'image/svg+xml'
when '.woff'
'font/woff'
when '.woff2'
'font/woff2'
when '.ttf'
'font/ttf'
when '.eot'
'application/vnd.ms-fontobject'
when '.mp3'
'audio/mpeg'
when '.wav'
'audio/wav'
when '.ogg'
'audio/ogg'
else
'application/octet-stream'
end
end
def render_not_found
render plain: 'Not Found', status: :not_found
end
end
end

View File

@@ -5,18 +5,110 @@
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%# Load game CSS %>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
<%# Google Fonts - Press Start 2P, VT323, Pixelify Sans %>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pixelify+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<%# Web Font Loader script to ensure fonts load properly %>
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
<script nonce="<%= content_security_policy_nonce %>">
WebFont.load({
google: {
families: ['Press Start 2P', 'VT323', 'Pixelify Sans']
},
active: function() {
console.log('Fonts loaded successfully');
}
});
</script>
<%# Load game CSS files %>
<link rel="stylesheet" href="/break_escape/css/main.css">
<link rel="stylesheet" href="/break_escape/css/utilities.css">
<link rel="stylesheet" href="/break_escape/css/notifications.css">
<link rel="stylesheet" href="/break_escape/css/panels.css">
<link rel="stylesheet" href="/break_escape/css/hud.css">
<link rel="stylesheet" href="/break_escape/css/minigames-framework.css">
<link rel="stylesheet" href="/break_escape/css/dusting.css">
<link rel="stylesheet" href="/break_escape/css/lockpicking.css">
<link rel="stylesheet" href="/break_escape/css/modals.css">
<link rel="stylesheet" href="/break_escape/css/notes.css">
<link rel="stylesheet" href="/break_escape/css/bluetooth-scanner.css">
<link rel="stylesheet" href="/break_escape/css/biometrics-minigame.css">
<link rel="stylesheet" href="/break_escape/css/container-minigame.css">
<link rel="stylesheet" href="/break_escape/css/phone-chat-minigame.css">
<link rel="stylesheet" href="/break_escape/css/person-chat-minigame.css">
<link rel="stylesheet" href="/break_escape/css/rfid-minigame.css">
<link rel="stylesheet" href="/break_escape/css/npc-interactions.css">
<link rel="stylesheet" href="/break_escape/css/pin.css">
<link rel="stylesheet" href="/break_escape/css/password-minigame.css">
<link rel="stylesheet" href="/break_escape/css/text-file-minigame.css">
<link rel="stylesheet" href="/break_escape/css/npc-barks.css">
<link rel="stylesheet" href="/break_escape/css/title-screen.css">
<link rel="stylesheet" href="/break_escape/css/inventory.css">
</head>
<body>
<%# Game container - Phaser will render here %>
<div id="break-escape-game"></div>
<div id="game-container"></div>
<%# Loading indicator %>
<div id="loading" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #00ff00; font-size: 24px; display: block;">
Loading game...
</div>
<%# Notification System %>
<div id="notification-container"></div>
<%# Toggle Buttons Container %>
<div id="toggle-buttons-container">
<!-- Biometrics is now handled as a minigame -->
</div>
<%# Inventory Container %>
<div id="inventory-container"></div>
<%# Laptop Popup %>
<div id="laptop-popup">
<div class="laptop-frame">
<div class="laptop-screen">
<div class="title-bar">
<span>Crypto Workstation</span>
<button class="minigame-close-button" onclick="closeLaptop()">×</button>
</div>
<div id="cyberchef-container">
<iframe id="cyberchef-frame" src=""></iframe>
</div>
</div>
</div>
</div>
<%# Password Modal %>
<div id="password-modal">
<div class="password-modal-content">
<div class="password-modal-title">
Enter Password
</div>
<input id="password-modal-input" type="password" autocomplete="off" autofocus>
<div class="password-modal-checkbox-container">
<input type="checkbox" id="password-modal-show">
<label for="password-modal-show" class="password-modal-checkbox-label">Show password</label>
</div>
<div class="password-modal-buttons">
<button id="password-modal-ok" class="password-modal-button">OK</button>
<button id="password-modal-cancel" class="password-modal-button">Cancel</button>
</div>
</div>
</div>
<%# Popup Overlay %>
<div class="popup-overlay"></div>
<%# Bootstrap configuration for client %>
<script nonce="<%= content_security_policy_nonce %>">
window.breakEscapeConfig = {
@@ -27,6 +119,11 @@
};
</script>
<%# Load required libraries before the game module %>
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js" nonce="<%= content_security_policy_nonce %>"></script>
<script src="https://cdn.jsdelivr.net/npm/easystarjs@0.4.4/bin/easystar-0.4.4.js" nonce="<%= content_security_policy_nonce %>"></script>
<script src="/break_escape/assets/vendor/ink.js" nonce="<%= content_security_policy_nonce %>"></script>
<%# Load game JavaScript (ES6 module) %>
<script type="module" src="/break_escape/js/main.js" nonce="<%= content_security_policy_nonce %>"></script>
</body>

View File

@@ -6,8 +6,6 @@
<%= csp_meta_tag %>
<%= yield :head %>
<%= stylesheet_link_tag "break_escape/application", media: "all" %>
</head>
<body>

View File

@@ -1,4 +1,11 @@
BreakEscape::Engine.routes.draw do
# Static files - match only static file paths (must come BEFORE resource routes)
# Note: These routes are automatically prefixed with /break_escape by the mount in the parent app
get '/css/*path', to: 'static_files#serve', constraints: { path: /.*/ }
get '/js/*path', to: 'static_files#serve', constraints: { path: /.*/ }
get '/assets/*path', to: 'static_files#serve', constraints: { path: /.*/ }
get '/stylesheets/*path', to: 'static_files#serve', constraints: { path: /.*/ }
# Mission selection
resources :missions, only: [:index, :show]

View File

@@ -42,14 +42,16 @@
#inventory-container {
position: fixed;
bottom: 0;
left: 50%;
transform: translateX(-50%);
left: 50%;
transform: translateX(-50%);
height: 80px;
display: flex;
align-items: center;
padding: 0 20px;
z-index: 1000;
font-family: 'VT323';
overflow-x: auto;
overflow-y: hidden;
}
#inventory-container::-webkit-scrollbar {
@@ -74,7 +76,7 @@
justify-content: center;
align-items: center;
position: relative;
background: rgb(149 157 216 / 80%);
background: transparent; /* keep slot outlines but no filled background per user request */
}
/* Pulse animation for newly added items */

View File

@@ -2,12 +2,10 @@
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
display: block;
background: #333;
font-smooth: never;
overflow: hidden;
}
#game-container {

View File

@@ -7,7 +7,8 @@
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.95);
z-index: 2000;
/* Raised so minigames appear above other UI (inventory, NPC barks, HUD, etc.) */
z-index: 11000;
display: flex;
flex-direction: column;
justify-content: center;

View File

@@ -439,25 +439,33 @@ export function preload() {
window.soundManagerPreload = new SoundManager(this);
window.soundManagerPreload.preloadSounds();
// Get scenario from URL parameter or use default
const urlParams = new URLSearchParams(window.location.search);
let scenarioFile = urlParams.get('scenario') || 'scenarios/ceo_exfil.json';
// Ensure scenario file has proper path prefix
if (!scenarioFile.startsWith('scenarios/')) {
scenarioFile = `scenarios/${scenarioFile}`;
// Load scenario from Rails API endpoint if available, otherwise try URL parameter
if (window.breakEscapeConfig?.apiBasePath) {
// Load scenario from Rails API endpoint
// Use absolute URL with origin to prevent Phaser baseURL from interfering
const scenarioUrl = `${window.location.origin}${window.breakEscapeConfig.apiBasePath}/scenario`;
this.load.json('gameScenarioJSON', scenarioUrl);
} else {
// Fallback to old behavior for standalone HTML files
const urlParams = new URLSearchParams(window.location.search);
let scenarioFile = urlParams.get('scenario') || 'scenarios/ceo_exfil.json';
// Ensure scenario file has proper path prefix
if (!scenarioFile.startsWith('scenarios/')) {
scenarioFile = `scenarios/${scenarioFile}`;
}
// Ensure .json extension
if (!scenarioFile.endsWith('.json')) {
scenarioFile = `${scenarioFile}.json`;
}
// Add cache buster query parameter to prevent browser caching
scenarioFile = `${scenarioFile}${scenarioFile.includes('?') ? '&' : '?'}v=${Date.now()}`;
// Load the specified scenario
this.load.json('gameScenarioJSON', scenarioFile);
}
// Ensure .json extension
if (!scenarioFile.endsWith('.json')) {
scenarioFile = `${scenarioFile}.json`;
}
// Add cache buster query parameter to prevent browser caching
scenarioFile = `${scenarioFile}${scenarioFile.includes('?') ? '&' : '?'}v=${Date.now()}`;
// Load the specified scenario
this.load.json('gameScenarioJSON', scenarioFile);
}

View File

@@ -66,7 +66,7 @@ export class BiometricsMinigame extends MinigameScene {
scannerHeader.className = 'biometrics-scanner-header';
scannerHeader.innerHTML = `
<div class="biometrics-scanner-title">
<img src="assets/objects/fingerprint.png" alt="Biometric Samples" class="scanner-icon">
<img src="/break_escape/assets/objects/fingerprint.png" alt="Biometric Samples" class="scanner-icon">
<span>Biometric Samples</span>
<span class="samples-count-header">0 samples</span>
</div>
@@ -81,7 +81,7 @@ export class BiometricsMinigame extends MinigameScene {
searchRoomContainer.className = 'biometrics-search-room-container';
searchRoomContainer.innerHTML = `
<button id="search-room-btn" class="biometrics-action-btn">
<span class="btn-icon"><img src="assets/icons/search.png" alt="Search" class="icon"></span>
<span class="btn-icon"><img src="/break_escape/assets/icons/search.png" alt="Search" class="icon"></span>
<span class="btn-text">Search Room for Fingerprints</span>
</button>
`;

View File

@@ -63,7 +63,7 @@ export class BluetoothScannerMinigame extends MinigameScene {
scannerHeader.className = 'bluetooth-scanner-header';
scannerHeader.innerHTML = `
<div class="bluetooth-scanner-title">
<img src="assets/objects/bluetooth_scanner.png" alt="Bluetooth Scanner" class="scanner-icon">
<img src="/break_escape/assets/objects/bluetooth_scanner.png" alt="Bluetooth Scanner" class="scanner-icon">
<span>Bluetooth Scanner</span>
</div>
<div class="bluetooth-scanner-status">
@@ -490,15 +490,15 @@ export class BluetoothScannerMinigame extends MinigameScene {
deviceContent += `</div></div>`;
} else if (device.nearby) {
// Fallback if signal strength not available
deviceContent += `<span class="bluetooth-device-icon"><img src="assets/icons/signal.png" alt="Signal" class="icon"></span>`;
deviceContent += `<span class="bluetooth-device-icon"><img src="/break_escape/assets/icons/signal.png" alt="Signal" class="icon"></span>`;
}
if (device.saved) {
deviceContent += `<span class="bluetooth-device-icon"><img src="assets/icons/disk.png" alt="Disk" class="icon"></span>`;
deviceContent += `<span class="bluetooth-device-icon"><img src="/break_escape/assets/icons/disk.png" alt="Disk" class="icon"></span>`;
}
if (device.inInventory) {
deviceContent += `<span class="bluetooth-device-icon"><img src="assets/icons/backpack.png" alt="Backpack" class="icon"></span>`;
deviceContent += `<span class="bluetooth-device-icon"><img src="/break_escape/assets/icons/backpack.png" alt="Backpack" class="icon"></span>`;
}
deviceContent += `</div></div>`;

View File

@@ -54,7 +54,7 @@ export class ContainerMinigame extends MinigameScene {
const notebookBtn = document.createElement('button');
notebookBtn.className = 'minigame-button';
notebookBtn.id = 'minigame-notebook-postit';
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
notebookBtn.innerHTML = '<img src="/break_escape/assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
// Insert before the cancel button (first child in controls)
this.controlsElement.insertBefore(notebookBtn, this.controlsElement.firstChild);
}
@@ -104,7 +104,7 @@ export class ContainerMinigame extends MinigameScene {
this.gameContainer.innerHTML = `
<div class="container-minigame">
<div class="container-image-section">
<img src="assets/objects/${this.containerItem.texture.key}.png"
<img src="/break_escape/assets/objects/${this.containerItem.texture.key}.png"
alt="${this.containerItem.scenarioData.name}"
class="container-image">
<div class="container-info">
@@ -130,7 +130,7 @@ export class ContainerMinigame extends MinigameScene {
createDesktopUI() {
this.gameContainer.innerHTML = `
<div class="container-image-section">
<img src="assets/objects/${this.containerItem.texture.key}.png"
<img src="/break_escape/assets/objects/${this.containerItem.texture.key}.png"
alt="${this.containerItem.scenarioData.name}"
class="container-image">
<div class="container-info">
@@ -184,7 +184,7 @@ export class ContainerMinigame extends MinigameScene {
const itemImg = document.createElement('img');
itemImg.className = 'container-content-item';
itemImg.src = `assets/objects/${item.type}.png`;
itemImg.src = `/break_escape/assets/objects/${item.type}.png`;
itemImg.alt = item.name;
itemImg.title = item.name;
@@ -230,7 +230,7 @@ export class ContainerMinigame extends MinigameScene {
const iconImg = document.createElement('img');
iconImg.className = 'desktop-icon-image';
iconImg.src = `assets/objects/${item.type}.png`;
iconImg.src = `/break_escape/assets/objects/${item.type}.png`;
iconImg.alt = item.name;
const iconLabel = document.createElement('div');

View File

@@ -3,7 +3,7 @@ import { MinigameScene } from '../framework/base-minigame.js';
// Load dusting-specific CSS
const dustingCSS = document.createElement('link');
dustingCSS.rel = 'stylesheet';
dustingCSS.href = 'css/dusting.css';
dustingCSS.href = '/break_escape/css/dusting.css';
dustingCSS.id = 'dusting-css';
if (!document.getElementById('dusting-css')) {
document.head.appendChild(dustingCSS);

View File

@@ -85,6 +85,12 @@ export const MinigameFramework = {
document.body.appendChild(container);
}
// Show the popup overlay to darken the background
const popupOverlay = document.querySelector('.popup-overlay');
if (popupOverlay) {
popupOverlay.classList.add('active');
}
// Create and start the minigame
const MinigameClass = this.registeredScenes[sceneType];
this.currentMinigame = new MinigameClass(container, params);
@@ -101,6 +107,12 @@ export const MinigameFramework = {
console.log('Cleaning up current minigame');
this.currentMinigame.cleanup();
// Hide the popup overlay
const popupOverlay = document.querySelector('.popup-overlay');
if (popupOverlay) {
popupOverlay.classList.remove('active');
}
// Remove minigame container only if it was auto-created
const container = document.querySelector('.minigame-container');
if (container && !container.hasAttribute('data-external')) {

View File

@@ -302,7 +302,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
const switchModeBtn = document.createElement('button');
switchModeBtn.className = 'minigame-button';
switchModeBtn.id = 'lockpicking-switch-mode-btn';
switchModeBtn.innerHTML = '<img src="assets/objects/lockpick.png" alt="Lockpick" class="icon-large"> Switch to Lockpicking';
switchModeBtn.innerHTML = '<img src="/break_escape/assets/objects/lockpick.png" alt="Lockpick" class="icon-large"> Switch to Lockpicking';
switchModeBtn.onclick = () => this.toolMgr.switchToPickMode();
buttonContainer.appendChild(switchModeBtn);
@@ -320,7 +320,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
const switchModeBtn = document.createElement('button');
switchModeBtn.className = 'minigame-button';
switchModeBtn.id = 'lockpicking-switch-to-keys-btn';
switchModeBtn.innerHTML = '<img src="assets/objects/key.png" alt="Key" class="icon-large"> Switch to Key Mode';
switchModeBtn.innerHTML = '<img src="/break_escape/assets/objects/key.png" alt="Key" class="icon-large"> Switch to Key Mode';
switchModeBtn.onclick = () => this.toolMgr.switchToKeyMode();
buttonContainer.appendChild(switchModeBtn);

View File

@@ -238,7 +238,7 @@ export class ToolManager {
const switchModeBtn = document.createElement('button');
switchModeBtn.className = 'minigame-button';
switchModeBtn.id = 'lockpicking-switch-mode-btn';
switchModeBtn.innerHTML = '<img src="assets/objects/lockpick.png" alt="Lockpick" class="icon-large"> Switch to Lockpicking';
switchModeBtn.innerHTML = '<img src="/break_escape/assets/objects/lockpick.png" alt="Lockpick" class="icon-large"> Switch to Lockpicking';
switchModeBtn.onclick = () => this.switchToPickMode();
buttonContainer.appendChild(switchModeBtn);

View File

@@ -100,7 +100,7 @@ export class NotesMinigame extends MinigameScene {
// Add star icon for important notes
if (isImportant) {
const starIcon = document.createElement('img');
starIcon.src = 'assets/icons/star.png';
starIcon.src = '/break_escape/assets/icons/star.png';
starIcon.alt = 'Important';
starIcon.className = 'notes-minigame-star';
noteTitle.appendChild(starIcon);
@@ -135,7 +135,7 @@ export class NotesMinigame extends MinigameScene {
// Add pencil icon
const pencilIcon = document.createElement('img');
pencilIcon.src = 'assets/icons/pencil.png';
pencilIcon.src = '/break_escape/assets/icons/pencil.png';
pencilIcon.alt = 'Edit';
editBtn.appendChild(pencilIcon);
@@ -163,7 +163,7 @@ export class NotesMinigame extends MinigameScene {
// Add pencil icon
const pencilIcon = document.createElement('img');
pencilIcon.src = 'assets/icons/pencil.png';
pencilIcon.src = '/break_escape/assets/icons/pencil.png';
pencilIcon.alt = 'Edit';
editBtn.appendChild(pencilIcon);
@@ -322,7 +322,7 @@ export class NotesMinigame extends MinigameScene {
// Add star icon for important notes
if (isImportant) {
const starIcon = document.createElement('img');
starIcon.src = 'assets/icons/star.png';
starIcon.src = '/break_escape/assets/icons/star.png';
starIcon.alt = 'Important';
starIcon.className = 'notes-minigame-star';
noteTitle.appendChild(starIcon);

View File

@@ -41,7 +41,7 @@ export class PasswordMinigame extends MinigameScene {
const notebookBtn = document.createElement('button');
notebookBtn.className = 'minigame-button';
notebookBtn.id = 'minigame-notebook-postit';
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
notebookBtn.innerHTML = '<img src="/break_escape/assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
// Insert before the cancel button (first child in controls)
this.controlsElement.insertBefore(notebookBtn, this.controlsElement.firstChild);
}
@@ -80,7 +80,7 @@ export class PasswordMinigame extends MinigameScene {
<div class="password-minigame-area">
${imageData ? `
<div class="password-image-section">
<img src="assets/objects/${imageData.imageFile}.png"
<img src="/break_escape/assets/objects/${imageData.imageFile}.png"
alt="${imageData.deviceName}"
class="password-image">
<div class="password-info">
@@ -100,7 +100,7 @@ export class PasswordMinigame extends MinigameScene {
placeholder="Enter password..."
maxlength="50">
<button type="button" class="toggle-password-btn" id="toggle-password">
<img class="icon-small" src="${this.gameData.showPassword ? 'assets/icons/visible.png' : 'assets/icons/hidden.png'}" alt="Toggle password visibility">
<img class="icon-small" src="/break_escape/assets/icons/${this.gameData.showPassword ? 'visible.png' : 'hidden.png'}" alt="Toggle password visibility">
</button>
</div>
</div>
@@ -128,7 +128,7 @@ export class PasswordMinigame extends MinigameScene {
<div class="password-controls">
${this.gameData.showKeyboard ? `
<button type="button" class="keyboard-toggle-btn" id="keyboard-toggle">
<img class="icon-keyboard" src="assets/objects/keyboard1.png" alt="Toggle keyboard">
<img class="icon-keyboard" src="/break_escape/assets/objects/keyboard1.png" alt="Toggle keyboard">
</button>
` : ''}
</div>

View File

@@ -93,7 +93,7 @@ export class PhoneChatMinigame extends MinigameScene {
const notebookBtn = document.createElement('button');
notebookBtn.className = 'minigame-button';
notebookBtn.id = 'minigame-notebook';
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
notebookBtn.innerHTML = '<img src="/break_escape/assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
// Insert before the cancel/close button
const cancelBtn = this.controlsElement.querySelector('#minigame-cancel');
if (cancelBtn) {

View File

@@ -268,11 +268,11 @@ export default class PhoneChatUI {
if (playing) {
// Show stop icon
playButton.innerHTML = '<img src="assets/icons/stop.png" alt="Stop" class="icon">';
playButton.innerHTML = '<img src="/break_escape/assets/icons/stop.png" alt="Stop" class="icon">';
playButton.title = 'Stop';
} else {
// Show play icon
playButton.innerHTML = '<img src="assets/icons/play.png" alt="Play" class="icon">';
playButton.innerHTML = '<img src="/break_escape/assets/icons/play.png" alt="Play" class="icon">';
playButton.title = 'Play';
}
}
@@ -489,13 +489,13 @@ export default class PhoneChatUI {
const playButton = document.createElement('div');
playButton.className = 'play-button';
const playIcon = document.createElement('img');
playIcon.src = 'assets/icons/play.png';
playIcon.src = '/break_escape/assets/icons/play.png';
playIcon.alt = 'Play';
playIcon.className = 'icon';
playButton.appendChild(playIcon);
const audioSprite = document.createElement('img');
audioSprite.src = 'assets/mini-games/audio.png';
audioSprite.src = '/break_escape/assets/mini-games/audio.png';
audioSprite.alt = 'Audio';
audioSprite.className = 'audio-sprite';

View File

@@ -160,7 +160,7 @@ export class PinMinigame extends MinigameScene {
// Add pin-cracker icon
this.pinCrackerIconElement = document.createElement('img');
this.pinCrackerIconElement.src = 'assets/objects/pin-cracker.png';
this.pinCrackerIconElement.src = '/break_escape/assets/objects/pin-cracker.png';
this.pinCrackerIconElement.alt = 'Pin Cracker';
this.pinCrackerIconElement.className = 'pin-minigame-cracker-icon';
this.pinCrackerIconElement.style.display = 'inline-block'; // Show by default when pin-cracker is available

View File

@@ -23,7 +23,7 @@ export class TextFileMinigame extends MinigameScene {
// Customize the header
this.headerElement.innerHTML = `
<h3><img src="assets/icons/text-file.png" alt="Document" class="icon"> ${this.textFileData.fileName}</h3>
<h3><img src="/break_escape/assets/icons/text-file.png" alt="Document" class="icon"> ${this.textFileData.fileName}</h3>
<p>Viewing text file contents</p>
`;
@@ -32,7 +32,7 @@ export class TextFileMinigame extends MinigameScene {
const notebookBtn = document.createElement('button');
notebookBtn.className = 'minigame-button';
notebookBtn.id = 'minigame-notebook';
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
notebookBtn.innerHTML = '<img src="/break_escape/assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
this.controlsElement.appendChild(notebookBtn);
// Change cancel button text to "Close"
@@ -64,7 +64,7 @@ export class TextFileMinigame extends MinigameScene {
</div>
<div class="file-header">
<div class="file-icon"><img src="assets/objects/text_file.png" alt="Document" class="icon-large"></div>
<div class="file-icon"><img src="/break_escape/assets/objects/text_file.png" alt="Document" class="icon-large"></div>
<div class="file-info">
<div class="file-name">${this.textFileData.fileName}</div>
<div class="file-meta">
@@ -77,7 +77,7 @@ export class TextFileMinigame extends MinigameScene {
<div class="file-content-area">
<div class="content-header">
<div class="content-actions">
<button class="action-btn" id="copy-btn" title="Copy to clipboard"><img src="assets/icons/copy-sm.png" alt="Clipboard" class="icon-small"> Copy</button>
<button class="action-btn" id="copy-btn" title="Copy to clipboard"><img src="/break_escape/assets/icons/copy-sm.png" alt="Clipboard" class="icon-small"> Copy</button>
<button class="action-btn" id="select-all-btn" title="Select all text">Select All</button>
</div>
</div>
@@ -88,7 +88,7 @@ export class TextFileMinigame extends MinigameScene {
${this.textFileData.observations ? `
<div class="file-observations">
<h4><img src="assets/icons/copy-sm.png" alt="Clipboard" class="icon-small"> Observations:</h4>
<h4><img src="/break_escape/assets/icons/copy-sm.png" alt="Clipboard" class="icon-small"> Observations:</h4>
<p>${this.textFileData.observations}</p>
</div>
` : ''}

View File

@@ -3,7 +3,7 @@ import { MinigameScene } from '../framework/base-minigame.js';
// Load title screen CSS
const titleScreenCSS = document.createElement('link');
titleScreenCSS.rel = 'stylesheet';
titleScreenCSS.href = 'css/title-screen.css';
titleScreenCSS.href = '/break_escape/css/title-screen.css';
titleScreenCSS.id = 'title-screen-css';
if (!document.getElementById('title-screen-css')) {
document.head.appendChild(titleScreenCSS);
@@ -26,7 +26,7 @@ export class TitleScreenMinigame extends MinigameScene {
this.container.innerHTML = `
<div class="title-screen-container">
<img src="assets/logos/hacktivity-logo.svg" alt="Hacktivity Logo" class="title-screen-logo">
<img src="/break_escape/assets/logos/hacktivity-logo.svg" alt="Hacktivity Logo" class="title-screen-logo">
<div class="title-screen-title">BreakEscape</div>
</div>
`;

View File

@@ -242,7 +242,7 @@ export function addToInventory(sprite) {
// Create inventory item
const itemImg = document.createElement('img');
itemImg.className = 'inventory-item';
itemImg.src = `assets/objects/${sprite.texture?.key || sprite.name || sprite.scenarioData?.type}.png`;
itemImg.src = `/break_escape/assets/objects/${sprite.texture?.key || sprite.name || sprite.scenarioData?.type}.png`;
itemImg.alt = sprite.scenarioData.name;
// Create tooltip
@@ -440,7 +440,7 @@ function updateKeyRingDisplay() {
// Create key ring item
const itemImg = document.createElement('img');
itemImg.className = 'inventory-item';
itemImg.src = keyRing.keys.length === 1 ? `assets/objects/key.png` : `assets/objects/key-ring.png`;
itemImg.src = keyRing.keys.length === 1 ? `/break_escape/assets/objects/key.png` : `/break_escape/assets/objects/key-ring.png`;
itemImg.alt = keyRing.keys.length === 1 ? keyRing.keys[0].scenarioData.name : 'Key Ring';
// Add data attributes for styling

View File

@@ -94,7 +94,7 @@ export function startLockpickingMinigame(lockable, scene, difficulty = 'medium',
} else {
// This is a regular item - use scenarioData
itemName = lockable?.scenarioData?.name || lockable?.name || 'Locked Item';
itemImage = lockable?.texture?.key ? `assets/objects/${lockable.texture.key}.png` : null;
itemImage = lockable?.texture?.key ? `/break_escape/assets/objects/${lockable.texture.key}.png` : null;
itemObservations = lockable?.scenarioData?.observations || '';
}
@@ -397,7 +397,7 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe
} else {
// This is a regular item - use scenarioData
itemName = lockable?.scenarioData?.name || lockable?.name || 'Locked Item';
itemImage = lockable?.texture?.key ? `assets/objects/${lockable.texture.key}.png` : null;
itemImage = lockable?.texture?.key ? `/break_escape/assets/objects/${lockable.texture.key}.png` : null;
itemObservations = lockable?.scenarioData?.observations || '';
}

View File

@@ -172,7 +172,7 @@ export class GameOverScreen {
mainMenu() {
// Navigate to scenario select or main menu
window.location.href = 'scenario_select.html';
window.location.href = '/break_escape/missions';
}
destroy() {

View File

@@ -34,7 +34,7 @@ export class HealthUI {
for (let i = 0; i < COMBAT_CONFIG.ui.maxHearts; i++) {
const heart = document.createElement('img');
heart.className = 'health-heart';
heart.src = 'assets/icons/heart.png';
heart.src = '/break_escape/assets/icons/heart.png';
heart.alt = 'HP';
heartsContainer.appendChild(heart);
this.hearts.push(heart);
@@ -84,15 +84,15 @@ export class HealthUI {
this.hearts.forEach((heart, index) => {
if (index < fullHearts) {
// Full heart
heart.src = 'assets/icons/heart.png';
heart.src = '/break_escape/assets/icons/heart.png';
heart.style.opacity = '1';
} else if (index === fullHearts && halfHeart) {
// Half heart
heart.src = 'assets/icons/heart-half.png';
heart.src = '/break_escape/assets/icons/heart-half.png';
heart.style.opacity = '1';
} else {
// Empty heart
heart.src = 'assets/icons/heart.png';
heart.src = '/break_escape/assets/icons/heart.png';
heart.style.opacity = '0.2';
}
});

View File

@@ -48,6 +48,9 @@ export const GAME_CONFIG = typeof Phaser !== 'undefined' ? {
height: 480, // Classic pixel art base resolution (scales cleanly: 1x=240, 2x=480, 3x=720, 4x=960)
parent: 'game-container',
pixelArt: true,
loader: {
baseURL: '/break_escape/'
},
scale: {
mode: Phaser.Scale.ENVELOP, // Fill entire container while maintaining aspect ratio
autoCenter: Phaser.Scale.CENTER_BOTH,

View File

@@ -16,7 +16,7 @@ export function openCryptoWorkstation() {
const cyberchefFrame = document.getElementById('cyberchef-frame');
// Set the iframe source to the CyberChef HTML file
cyberchefFrame.src = 'assets/cyberchef/CyberChef_v10.19.4.html';
cyberchefFrame.src = '/break_escape/assets/cyberchef/CyberChef_v10.19.4.html';
// Show the laptop popup
laptopPopup.style.display = 'block';