diff --git a/assets/mini-games/desktop-wallpaper.png b/assets/mini-games/desktop-wallpaper.png
new file mode 100644
index 0000000..a3f589e
Binary files /dev/null and b/assets/mini-games/desktop-wallpaper.png differ
diff --git a/css/container-minigame.css b/css/container-minigame.css
index 6dae6c1..af8a541 100644
--- a/css/container-minigame.css
+++ b/css/container-minigame.css
@@ -8,6 +8,141 @@
gap: 20px;
}
+/* Desktop Mode Styles */
+.container-minigame.desktop-mode {
+ padding: 0;
+ gap: 0;
+ background: #000;
+}
+
+.desktop-background {
+ flex: 1;
+ position: relative;
+ background: #000;
+ overflow: hidden;
+}
+
+.desktop-wallpaper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-image: url('../assets/mini-games/desktop-wallpaper.png');
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ image-rendering: pixelated;
+ image-rendering: -moz-crisp-edges;
+ image-rendering: crisp-edges;
+ opacity: 0.8;
+}
+
+.desktop-wallpaper::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.2);
+}
+
+.desktop-icons {
+ position: relative;
+ z-index: 2;
+ width: 100%;
+ height: 100%;
+ padding: 20px;
+}
+
+.desktop-icon {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 80px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.desktop-icon:hover {
+ transform: scale(1.1);
+}
+
+.desktop-icon-image {
+ width: 48px;
+ height: 48px;
+ object-fit: contain;
+ image-rendering: pixelated;
+ image-rendering: -moz-crisp-edges;
+ image-rendering: crisp-edges;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 8px;
+ padding: 4px;
+ border: 2px solid transparent;
+}
+
+.desktop-icon:hover .desktop-icon-image {
+ border-color: #00ff00;
+ background: rgba(0, 255, 0, 0.1);
+}
+
+.desktop-icon-label {
+ font-family: 'Press Start 2P', monospace;
+ font-size: 8px;
+ color: white;
+ text-align: center;
+ margin-top: 4px;
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
+ word-wrap: break-word;
+ max-width: 80px;
+}
+
+.desktop-taskbar {
+ background: rgba(0, 0, 0, 0.8);
+ border-top: 2px solid #333;
+ padding: 10px 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ min-height: 50px;
+}
+
+.desktop-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.desktop-title {
+ font-family: 'Press Start 2P', monospace;
+ font-size: 10px;
+ color: #00ff00;
+}
+
+.desktop-subtitle {
+ font-family: 'VT323', monospace;
+ font-size: 14px;
+ color: #ccc;
+}
+
+.desktop-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.empty-desktop {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-family: 'Press Start 2P', monospace;
+ font-size: 12px;
+ color: #666;
+ text-align: center;
+}
+
.container-image-section {
display: flex;
align-items: center;
diff --git a/css/minigames.css b/css/minigames.css
index ed6ba0f..da49885 100644
--- a/css/minigames.css
+++ b/css/minigames.css
@@ -190,3 +190,385 @@
transition: width 0.3s ease;
border-radius: 5px;
}
+
+/* Password Minigame Specific Styles */
+.password-minigame-area {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ padding: 20px;
+ gap: 15px;
+ background: #1a1a1a;
+ position: relative;
+}
+
+.password-input-container {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.monitor-bezel {
+ background: #2a2a2a;
+ border: 8px solid #1a1a1a;
+ border-radius: 15px;
+ padding: 20px;
+ box-shadow:
+ inset 0 0 20px rgba(0, 0, 0, 0.5),
+ 0 0 30px rgba(0, 0, 0, 0.8);
+ position: relative;
+ background-image: url('../assets/mini-games/desktop-wallpaper.png');
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ image-rendering: pixelated;
+ image-rendering: -moz-crisp-edges;
+ image-rendering: crisp-edges;
+}
+
+.monitor-bezel::before {
+ content: '';
+ position: absolute;
+ top: -4px;
+ left: -4px;
+ right: -4px;
+ bottom: -4px;
+ background: linear-gradient(45deg, #444, #666, #444);
+ border-radius: 19px;
+ z-index: -1;
+}
+
+.monitor-bezel::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.4);
+ border-radius: 7px;
+ z-index: 1;
+}
+
+.monitor-screen {
+ border: 2px solid #333;
+ border-radius: 8px;
+ padding: 15px;
+ min-height: 120px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ z-index: 2;
+}
+
+.monitor-screen::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(135deg, rgba(0, 255, 0, 0.1), rgba(0, 255, 255, 0.1));
+ border-radius: 6px;
+ z-index: 1;
+}
+
+.monitor-screen > * {
+ position: relative;
+ z-index: 2;
+}
+
+.password-input-container label {
+ font-size: 12px;
+ color: #00ff00;
+ margin-bottom: 5px;
+}
+
+.password-field-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.password-field {
+ width: 100%;
+ padding: 12px 45px 12px 12px;
+ background: #1a1a1a;
+ border: 2px solid #00ff00;
+ border-radius: 5px;
+ color: white;
+ font-family: 'Press Start 2P', monospace;
+ font-size: 10px;
+ outline: none;
+ transition: border-color 0.3s ease;
+}
+
+.password-field:focus {
+ border-color: #00ffff;
+ box-shadow: 0 0 10px rgba(0, 255, 255, 0.3);
+}
+
+.password-field::placeholder {
+ color: #666;
+}
+
+.toggle-password-btn {
+ position: absolute;
+ right: 8px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ color: #00ff00;
+ cursor: pointer;
+ font-size: 16px;
+ padding: 5px;
+ border-radius: 3px;
+ transition: background-color 0.3s ease;
+}
+
+.toggle-password-btn:hover {
+ background: rgba(0, 255, 0, 0.1);
+}
+
+.password-hint-container {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.hint-btn {
+ background: #f39c12;
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-family: 'Press Start 2P', monospace;
+ font-size: 8px;
+ transition: background 0.3s ease;
+ align-self: flex-start;
+}
+
+.hint-btn:hover {
+ background: #e67e22;
+}
+
+.password-hint {
+ background: rgba(243, 156, 18, 0.1);
+ border: 1px solid #f39c12;
+ border-radius: 5px;
+ padding: 10px;
+ font-size: 10px;
+ color: #f39c12;
+}
+
+.postit-note {
+ background: #ffff88;
+ border: 1px solid #ddd;
+ border-radius: 3px;
+ padding: 15px;
+ margin: 10px 0;
+ box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3);
+ position: relative;
+ transform: rotate(-2deg);
+ font-family: 'Press Start 2P', monospace;
+ font-size: 8px;
+ color: #333;
+ max-width: 200px;
+ word-wrap: break-word;
+}
+
+.postit-note::before {
+ content: '';
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ width: 0;
+ height: 0;
+ border-left: 15px solid transparent;
+ border-top: 15px solid #f0f0f0;
+}
+
+.postit-note::after {
+ content: '';
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ width: 8px;
+ height: 8px;
+ background: #ff6b6b;
+ border-radius: 50%;
+ box-shadow: 0 0 0 1px #fff, 0 0 0 2px #ff6b6b;
+}
+
+.onscreen-keyboard {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ background: #2a2a2a;
+ border: 2px solid #444;
+ border-radius: 8px;
+ padding: 10px;
+ margin: 10px 0;
+}
+
+.keyboard-row {
+ display: flex;
+ justify-content: center;
+ gap: 3px;
+ flex-wrap: wrap;
+}
+
+.key {
+ background: #444;
+ color: white;
+ border: 1px solid #666;
+ border-radius: 4px;
+ padding: 8px 12px;
+ cursor: pointer;
+ font-family: 'Press Start 2P', monospace;
+ font-size: 8px;
+ min-width: 35px;
+ text-align: center;
+ transition: all 0.2s ease;
+ user-select: none;
+}
+
+.key:hover {
+ background: #555;
+ border-color: #00ff00;
+}
+
+.key:active {
+ background: #00ff00;
+ color: black;
+ transform: scale(0.95);
+}
+
+.key-backspace {
+ background: #e74c3c;
+ min-width: 60px;
+}
+
+.key-backspace:hover {
+ background: #c0392b;
+}
+
+.key-space {
+ background: #3498db;
+ min-width: 100px;
+}
+
+.key-space:hover {
+ background: #2980b9;
+}
+
+.key-special {
+ background: #9b59b6;
+ min-width: 80px;
+}
+
+.key-special:hover {
+ background: #8e44ad;
+}
+
+.password-actions {
+ display: flex;
+ justify-content: center;
+ gap: 15px;
+ margin-top: 10px;
+}
+
+.submit-btn {
+ background: #2ecc71;
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-family: 'Press Start 2P', monospace;
+ font-size: 10px;
+ transition: background 0.3s ease;
+}
+
+.submit-btn:hover {
+ background: #27ae60;
+}
+
+.submit-btn:active {
+ background: #229954;
+}
+
+.cancel-btn {
+ background: #e74c3c;
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-family: 'Press Start 2P', monospace;
+ font-size: 10px;
+ transition: background 0.3s ease;
+}
+
+.cancel-btn:hover {
+ background: #c0392b;
+}
+
+.cancel-btn:active {
+ background: #a93226;
+}
+
+.attempts-counter {
+ text-align: center;
+ font-size: 10px;
+ color: #f39c12;
+ background: rgba(243, 156, 18, 0.1);
+ border: 1px solid #f39c12;
+ border-radius: 5px;
+ padding: 8px;
+ margin-top: 10px;
+}
+
+.attempts-counter span {
+ color: #e74c3c;
+ font-weight: bold;
+}
+
+/* Responsive design for smaller screens */
+@media (max-width: 768px) {
+ .onscreen-keyboard {
+ padding: 5px;
+ }
+
+ .key {
+ padding: 6px 8px;
+ font-size: 7px;
+ min-width: 30px;
+ }
+
+ .key-backspace {
+ min-width: 50px;
+ }
+
+ .key-space {
+ min-width: 80px;
+ }
+
+ .key-special {
+ min-width: 60px;
+ }
+
+ .password-field {
+ font-size: 9px;
+ padding: 10px 40px 10px 10px;
+ }
+
+ .submit-btn, .cancel-btn {
+ padding: 10px 20px;
+ font-size: 9px;
+ }
+}
diff --git a/index.html b/index.html
index 1db810c..0795eb2 100644
--- a/index.html
+++ b/index.html
@@ -41,6 +41,7 @@
+
diff --git a/js/minigames/container/container-minigame.js b/js/minigames/container/container-minigame.js
index debacc0..a0bcf0e 100644
--- a/js/minigames/container/container-minigame.js
+++ b/js/minigames/container/container-minigame.js
@@ -8,6 +8,27 @@ export class ContainerMinigame extends MinigameScene {
this.containerItem = params.containerItem;
this.contents = params.contents || [];
this.isTakeable = params.isTakeable || false;
+
+ // Auto-detect desktop mode for PC/tablet containers
+ this.desktopMode = params.desktopMode || this.shouldUseDesktopMode();
+ }
+
+ shouldUseDesktopMode() {
+ // Check if the container is a PC, tablet, or computer-related device
+ const containerName = this.containerItem?.scenarioData?.name?.toLowerCase() || '';
+ const containerType = this.containerItem?.scenarioData?.type?.toLowerCase() || '';
+ const containerImage = this.containerItem?.name?.toLowerCase() || '';
+
+ // Keywords that indicate desktop/computer devices
+ const desktopKeywords = [
+ 'computer', 'pc', 'laptop', 'desktop', 'terminal', 'workstation',
+ 'tablet', 'ipad', 'surface', 'monitor', 'screen', 'display',
+ 'server', 'mainframe', 'console', 'kiosk', 'smartboard'
+ ];
+
+ // Check if any keyword matches
+ const allText = `${containerName} ${containerType} ${containerImage}`.toLowerCase();
+ return desktopKeywords.some(keyword => allText.includes(keyword));
}
init() {
@@ -27,6 +48,20 @@ export class ContainerMinigame extends MinigameScene {
}
createContainerUI() {
+ if (this.desktopMode) {
+ this.createDesktopUI();
+ } else {
+ this.createStandardUI();
+ }
+
+ // Populate contents
+ this.populateContents();
+
+ // Set up event listeners
+ this.setupEventListeners();
+ }
+
+ createStandardUI() {
this.gameContainer.innerHTML = `
@@ -52,15 +87,41 @@ export class ContainerMinigame extends MinigameScene {
`;
-
- // Populate contents
- this.populateContents();
-
- // Set up event listeners
- this.setupEventListeners();
+ }
+
+ createDesktopUI() {
+ this.gameContainer.innerHTML = `
+
+
+
+
+
+ ${this.containerItem.scenarioData.name}
+ ${this.containerItem.scenarioData.observations || ''}
+
+
+ ${this.isTakeable ? '' : ''}
+
+
+
+
+ `;
}
populateContents() {
+ if (this.desktopMode) {
+ this.populateDesktopIcons();
+ } else {
+ this.populateStandardContents();
+ }
+ }
+
+ populateStandardContents() {
const contentsGrid = document.getElementById('container-contents-grid');
if (!contentsGrid) return;
@@ -107,6 +168,57 @@ export class ContainerMinigame extends MinigameScene {
});
}
+ populateDesktopIcons() {
+ const desktopIcons = document.getElementById('desktop-icons');
+ if (!desktopIcons) return;
+
+ if (this.contents.length === 0) {
+ desktopIcons.innerHTML = 'Desktop is empty
';
+ return;
+ }
+
+ this.contents.forEach((item, index) => {
+ const icon = document.createElement('div');
+ icon.className = 'desktop-icon';
+
+ const iconImg = document.createElement('img');
+ iconImg.className = 'desktop-icon-image';
+ iconImg.src = `assets/objects/${item.type}.png`;
+ iconImg.alt = item.name;
+
+ const iconLabel = document.createElement('div');
+ iconLabel.className = 'desktop-icon-label';
+ iconLabel.textContent = item.name;
+
+ // Add item data
+ iconImg.scenarioData = item;
+ iconImg.name = item.type;
+ iconImg.objectId = `desktop_${index}`;
+
+ // Add click handler for taking items
+ if (item.takeable) {
+ icon.style.cursor = 'pointer';
+
+ // Special handling for notes - trigger notes minigame instead of taking
+ if (item.type === 'notes' && item.readable && item.text) {
+ icon.addEventListener('click', () => this.handleNotesItem(item, iconImg));
+ } else {
+ icon.addEventListener('click', () => this.takeItem(item, iconImg));
+ }
+ }
+
+ // Position icon randomly on desktop
+ const x = Math.random() * 70 + 10; // 10% to 80% of width
+ const y = Math.random() * 60 + 10; // 10% to 70% of height
+ icon.style.left = `${x}%`;
+ icon.style.top = `${y}%`;
+
+ icon.appendChild(iconImg);
+ icon.appendChild(iconLabel);
+ desktopIcons.appendChild(icon);
+ });
+ }
+
setupEventListeners() {
// Take container button
const takeContainerBtn = document.getElementById('take-container-btn');
@@ -254,8 +366,13 @@ export class ContainerMinigame extends MinigameScene {
}
// Function to start the container minigame
-export function startContainerMinigame(containerItem, contents, isTakeable = false) {
- console.log('Starting container minigame', { containerItem, contents, isTakeable });
+export function startContainerMinigame(containerItem, contents, isTakeable = false, desktopMode = null) {
+ // Auto-detect desktop mode if not explicitly set
+ if (desktopMode === null) {
+ desktopMode = shouldUseDesktopModeForContainer(containerItem);
+ }
+
+ console.log('Starting container minigame', { containerItem, contents, isTakeable, desktopMode });
// Initialize the minigame framework if not already done
if (!window.MinigameFramework) {
@@ -273,6 +390,7 @@ export function startContainerMinigame(containerItem, contents, isTakeable = fal
containerItem: containerItem,
contents: contents,
isTakeable: isTakeable,
+ desktopMode: desktopMode,
cancelText: 'Close',
showCancel: true,
onComplete: (success, result) => {
@@ -281,6 +399,25 @@ export function startContainerMinigame(containerItem, contents, isTakeable = fal
});
}
+// Helper function to determine if a container should use desktop mode
+function shouldUseDesktopModeForContainer(containerItem) {
+ // Check if the container is a PC, tablet, or computer-related device
+ const containerName = containerItem?.scenarioData?.name?.toLowerCase() || '';
+ const containerType = containerItem?.scenarioData?.type?.toLowerCase() || '';
+ const containerImage = containerItem?.name?.toLowerCase() || '';
+
+ // Keywords that indicate desktop/computer devices
+ const desktopKeywords = [
+ 'computer', 'pc', 'laptop', 'desktop', 'terminal', 'workstation',
+ 'tablet', 'ipad', 'surface', 'monitor', 'screen', 'display',
+ 'server', 'mainframe', 'console', 'kiosk', 'smartboard'
+ ];
+
+ // Check if any keyword matches
+ const allText = `${containerName} ${containerType} ${containerImage}`.toLowerCase();
+ return desktopKeywords.some(keyword => allText.includes(keyword));
+}
+
// Function to return to container after notes minigame
export function returnToContainerAfterNotes() {
console.log('Returning to container after notes minigame');
diff --git a/js/minigames/index.js b/js/minigames/index.js
index 1cc84ba..50f7020 100644
--- a/js/minigames/index.js
+++ b/js/minigames/index.js
@@ -12,6 +12,7 @@ export { LockpickSetMinigame, startLockpickSetMinigame } from './lockpick/lockpi
export { ContainerMinigame, startContainerMinigame, returnToContainerAfterNotes } from './container/container-minigame.js';
export { PhoneMessagesMinigame, returnToPhoneAfterNotes } from './phone/phone-messages-minigame.js';
export { PinMinigame, startPinMinigame } from './pin/pin-minigame.js';
+export { PasswordMinigame } from './password/password-minigame.js';
// Initialize the global minigame framework for backward compatibility
import { MinigameFramework } from './framework/minigame-manager.js';
@@ -61,6 +62,9 @@ import { PhoneMessagesMinigame, returnToPhoneAfterNotes } from './phone/phone-me
// Import the PIN minigame
import { PinMinigame, startPinMinigame } from './pin/pin-minigame.js';
+// Import the password minigame
+import { PasswordMinigame } from './password/password-minigame.js';
+
// Register minigames
MinigameFramework.registerScene('lockpicking', LockpickingMinigamePhaser); // Use Phaser version as default
MinigameFramework.registerScene('lockpicking-phaser', LockpickingMinigamePhaser); // Keep explicit phaser name
@@ -72,6 +76,7 @@ MinigameFramework.registerScene('lockpick-set', LockpickSetMinigame);
MinigameFramework.registerScene('container', ContainerMinigame);
MinigameFramework.registerScene('phone-messages', PhoneMessagesMinigame);
MinigameFramework.registerScene('pin', PinMinigame);
+MinigameFramework.registerScene('password', PasswordMinigame);
// Make minigame functions available globally
window.startNotesMinigame = startNotesMinigame;
diff --git a/js/minigames/password/password-minigame.js b/js/minigames/password/password-minigame.js
new file mode 100644
index 0000000..efd8fbb
--- /dev/null
+++ b/js/minigames/password/password-minigame.js
@@ -0,0 +1,357 @@
+import { MinigameScene } from '../framework/base-minigame.js';
+
+export class PasswordMinigame extends MinigameScene {
+ constructor(container, params) {
+ super(container, params);
+
+ // Initialize password-specific state
+ this.gameData = {
+ password: params.password || '',
+ passwordHint: params.passwordHint || '',
+ showHint: params.showHint || false,
+ showKeyboard: params.showKeyboard || false,
+ maxAttempts: params.maxAttempts || 3,
+ attempts: 0,
+ showPassword: false,
+ postitNote: params.postitNote || '',
+ showPostit: params.showPostit || false
+ };
+
+ // Store the correct password for validation
+ this.correctPassword = params.password || '';
+ }
+
+ init() {
+ // Call parent init to set up basic UI structure
+ super.init();
+
+ // Customize the header
+ this.headerElement.innerHTML = `
+ ${this.params.title || 'Password Entry'}
+ Enter the correct password to proceed
+ `;
+
+ // Set up the password interface
+ this.setupPasswordInterface();
+
+ // Set up event listeners
+ this.setupEventListeners();
+ }
+
+ setupPasswordInterface() {
+ // Create the password entry interface
+ this.gameContainer.innerHTML = `
+
+ ${this.gameData.showPostit && this.gameData.postitNote ? `
+
+ ${this.gameData.postitNote}
+
+ ` : ''}
+
+
+
+
+
+ ${this.gameData.showHint ? `
+
+
+
+ Hint: ${this.gameData.passwordHint}
+
+
+ ` : ''}
+
+
+
+ ${this.gameData.showKeyboard ? `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ` : ''}
+
+
+
+
+
+
+
+ Attempts: ${this.gameData.attempts}/${this.gameData.maxAttempts}
+
+
+ `;
+
+ // Get references to important elements
+ this.passwordField = document.getElementById('password-field');
+ this.togglePasswordBtn = document.getElementById('toggle-password');
+ this.submitBtn = document.getElementById('submit-password');
+ this.cancelBtn = document.getElementById('cancel-password');
+ this.attemptsDisplay = document.getElementById('attempts-display');
+
+ // Focus the password field
+ if (this.passwordField) {
+ this.passwordField.focus();
+ }
+ }
+
+ setupEventListeners() {
+ // Password field events
+ if (this.passwordField) {
+ this.addEventListener(this.passwordField, 'keydown', (event) => {
+ this.handleKeyPress(event);
+ });
+
+ this.addEventListener(this.passwordField, 'input', (event) => {
+ this.handlePasswordInput(event);
+ });
+ }
+
+ // Toggle password visibility
+ if (this.togglePasswordBtn) {
+ this.addEventListener(this.togglePasswordBtn, 'click', () => {
+ this.togglePasswordVisibility();
+ });
+ }
+
+ // Submit button
+ if (this.submitBtn) {
+ this.addEventListener(this.submitBtn, 'click', () => {
+ this.submitPassword();
+ });
+ }
+
+ // Cancel button
+ if (this.cancelBtn) {
+ this.addEventListener(this.cancelBtn, 'click', () => {
+ this.cancelPassword();
+ });
+ }
+
+ // Hint button
+ const hintBtn = document.getElementById('show-hint');
+ if (hintBtn) {
+ this.addEventListener(hintBtn, 'click', () => {
+ this.toggleHint();
+ });
+ }
+
+ // Onscreen keyboard
+ const keyboard = document.getElementById('onscreen-keyboard');
+ if (keyboard) {
+ this.addEventListener(keyboard, 'click', (event) => {
+ this.handleKeyboardClick(event);
+ });
+ }
+ }
+
+ start() {
+ // Call parent start
+ super.start();
+
+ console.log("Password minigame started");
+ }
+
+ handleKeyPress(event) {
+ if (!this.gameState.isActive) return;
+
+ switch(event.key) {
+ case 'Enter':
+ event.preventDefault();
+ this.submitPassword();
+ break;
+ case 'Escape':
+ event.preventDefault();
+ this.cancelPassword();
+ break;
+ }
+ }
+
+ handlePasswordInput(event) {
+ // Update the internal password state
+ this.gameData.password = event.target.value;
+ }
+
+ togglePasswordVisibility() {
+ this.gameData.showPassword = !this.gameData.showPassword;
+
+ // Update input type
+ this.passwordField.type = this.gameData.showPassword ? 'text' : 'password';
+
+ // Update button icon
+ this.togglePasswordBtn.textContent = this.gameData.showPassword ? '👁️' : '👁️🗨️';
+ }
+
+ toggleHint() {
+ const hintElement = document.getElementById('password-hint');
+ const hintBtn = document.getElementById('show-hint');
+
+ if (hintElement && hintBtn) {
+ if (hintElement.style.display === 'none') {
+ hintElement.style.display = 'block';
+ hintBtn.textContent = 'Hide Hint';
+ } else {
+ hintElement.style.display = 'none';
+ hintBtn.textContent = 'Show Hint';
+ }
+ }
+ }
+
+ handleKeyboardClick(event) {
+ if (!this.gameState.isActive) return;
+
+ const key = event.target;
+ if (!key.classList.contains('key')) return;
+
+ const keyValue = key.dataset.key;
+
+ if (keyValue === 'Enter') {
+ this.submitPassword();
+ } else if (keyValue === 'Escape') {
+ this.cancelPassword();
+ } else if (keyValue === 'Backspace') {
+ this.passwordField.value = this.passwordField.value.slice(0, -1);
+ this.gameData.password = this.passwordField.value;
+ } else if (keyValue === ' ') {
+ this.passwordField.value += ' ';
+ this.gameData.password = this.passwordField.value;
+ } else if (keyValue && keyValue.length === 1) {
+ this.passwordField.value += keyValue;
+ this.gameData.password = this.passwordField.value;
+ }
+
+ // Keep focus on password field
+ this.passwordField.focus();
+ }
+
+ submitPassword() {
+ if (!this.gameState.isActive) return;
+
+ const enteredPassword = this.passwordField.value.trim();
+
+ if (!enteredPassword) {
+ this.showFailure("Please enter a password", false, 2000);
+ return;
+ }
+
+ this.gameData.attempts++;
+ this.attemptsDisplay.textContent = this.gameData.attempts;
+
+ if (enteredPassword === this.correctPassword) {
+ this.passwordCorrect();
+ } else {
+ this.passwordIncorrect();
+ }
+ }
+
+ passwordCorrect() {
+ this.cleanup();
+ this.showSuccess("Password accepted! Access granted.", true, 3000);
+
+ // Set game result for the callback
+ this.gameResult = {
+ success: true,
+ password: this.gameData.password,
+ attempts: this.gameData.attempts
+ };
+ }
+
+ passwordIncorrect() {
+ if (this.gameData.attempts >= this.gameData.maxAttempts) {
+ this.passwordFailed();
+ } else {
+ this.showFailure(`Incorrect password. ${this.gameData.maxAttempts - this.gameData.attempts} attempts remaining.`, false, 3000);
+
+ // Clear the password field
+ this.passwordField.value = '';
+ this.gameData.password = '';
+ this.passwordField.focus();
+ }
+ }
+
+ passwordFailed() {
+ this.cleanup();
+ this.showFailure("Maximum attempts exceeded. Access denied.", true, 3000);
+
+ this.gameResult = {
+ success: false,
+ reason: 'max_attempts_exceeded',
+ attempts: this.gameData.attempts
+ };
+ }
+
+ cancelPassword() {
+ this.cleanup();
+ this.showFailure("Password entry cancelled.", true, 2000);
+
+ this.gameResult = {
+ success: false,
+ reason: 'cancelled',
+ attempts: this.gameData.attempts
+ };
+ }
+
+ cleanup() {
+ // Call parent cleanup (handles event listeners)
+ super.cleanup();
+ }
+}
diff --git a/js/systems/minigame-starters.js b/js/systems/minigame-starters.js
index 65b9065..c0b20da 100644
--- a/js/systems/minigame-starters.js
+++ b/js/systems/minigame-starters.js
@@ -264,8 +264,58 @@ export function startPinMinigame(lockable, type, correctPin, callback) {
});
}
+export function startPasswordMinigame(lockable, type, correctPassword, callback, options = {}) {
+ console.log('Starting password minigame for', type, 'with password:', correctPassword);
+
+ // Initialize the minigame framework if not already done
+ if (!window.MinigameFramework) {
+ console.error('MinigameFramework not available');
+ // Fallback to simple prompt
+ const passwordInput = prompt(`Enter password:`);
+ if (passwordInput === correctPassword) {
+ console.log('PASSWORD SUCCESS (fallback)');
+ window.gameAlert(`Correct password! The ${type} is now unlocked.`, 'success', 'Password Accepted', 4000);
+ callback(true);
+ } else if (passwordInput !== null) {
+ console.log('PASSWORD FAIL (fallback)');
+ window.gameAlert("Incorrect password.", 'error', 'Password Rejected', 3000);
+ callback(false);
+ }
+ return;
+ }
+
+ // Use the advanced minigame framework
+ if (!window.MinigameFramework.mainGameScene) {
+ window.MinigameFramework.init(window.game);
+ }
+
+ // Start the password minigame
+ window.MinigameFramework.startMinigame('password', null, {
+ title: `Enter password for ${type}`,
+ password: correctPassword,
+ passwordHint: options.passwordHint || '',
+ showHint: options.showHint || false,
+ showKeyboard: options.showKeyboard || false,
+ maxAttempts: options.maxAttempts || 3,
+ postitNote: options.postitNote || '',
+ showPostit: options.showPostit || false,
+ onComplete: (success, result) => {
+ if (success) {
+ console.log('PASSWORD MINIGAME SUCCESS');
+ window.gameAlert(`Correct password! The ${type} is now unlocked.`, 'success', 'Password Accepted', 4000);
+ callback(true);
+ } else {
+ console.log('PASSWORD MINIGAME FAILED');
+ window.gameAlert("Failed to enter correct password.", 'error', 'Password Rejected', 3000);
+ callback(false);
+ }
+ }
+ });
+}
+
// Export for global access
window.startLockpickingMinigame = startLockpickingMinigame;
window.startKeySelectionMinigame = startKeySelectionMinigame;
window.startPinMinigame = startPinMinigame;
+window.startPasswordMinigame = startPasswordMinigame;
diff --git a/js/systems/unlock-system.js b/js/systems/unlock-system.js
index e3089a3..2a8e375 100644
--- a/js/systems/unlock-system.js
+++ b/js/systems/unlock-system.js
@@ -10,7 +10,7 @@
import { DOOR_ALIGN_OVERLAP } from '../utils/constants.js';
import { rooms } from '../core/rooms.js';
import { unlockDoor } from './doors.js';
-import { startLockpickingMinigame, startKeySelectionMinigame, startPinMinigame } from './minigame-starters.js';
+import { startLockpickingMinigame, startKeySelectionMinigame, startPinMinigame, startPasswordMinigame } from './minigame-starters.js';
// Helper function to check if two rectangles overlap
function boundsOverlap(rect1, rect2) {
@@ -106,29 +106,22 @@ export function handleUnlock(lockable, type) {
case 'password':
console.log('PASSWORD REQUESTED');
- if (window.showPasswordModal) {
- window.showPasswordModal(function(passwordInput) {
- if (passwordInput === lockRequirements.requires) {
- unlockTarget(lockable, type, lockable.layer);
- console.log('PASSWORD SUCCESS');
- window.gameAlert(`Correct password! The ${type} is now unlocked.`, 'success', 'Password Accepted', 4000);
- } else if (passwordInput !== null) {
- console.log('PASSWORD FAIL');
- window.gameAlert("Incorrect password.", 'error', 'Password Rejected', 3000);
- }
- });
- } else {
- // Fallback to prompt
- const passwordInput = prompt(`Enter password:`);
- if (passwordInput === lockRequirements.requires) {
+
+ // Get password options from the lockable object
+ const passwordOptions = {
+ passwordHint: lockable.passwordHint || lockable.scenarioData?.passwordHint || '',
+ showHint: lockable.showHint || lockable.scenarioData?.showHint || false,
+ showKeyboard: lockable.showKeyboard || lockable.scenarioData?.showKeyboard || false,
+ maxAttempts: lockable.maxAttempts || lockable.scenarioData?.maxAttempts || 3,
+ postitNote: lockable.postitNote || lockable.scenarioData?.postitNote || '',
+ showPostit: lockable.showPostit || lockable.scenarioData?.showPostit || false
+ };
+
+ startPasswordMinigame(lockable, type, lockRequirements.requires, (success) => {
+ if (success) {
unlockTarget(lockable, type, lockable.layer);
- console.log('PASSWORD SUCCESS');
- window.gameAlert(`Correct password! The ${type} is now unlocked.`, 'success', 'Password Accepted', 4000);
- } else if (passwordInput !== null) {
- console.log('PASSWORD FAIL');
- window.gameAlert("Incorrect password.", 'error', 'Password Rejected', 3000);
}
- }
+ }, passwordOptions);
break;
case 'biometric':
diff --git a/password-minigame-example.json b/password-minigame-example.json
new file mode 100644
index 0000000..f38e988
--- /dev/null
+++ b/password-minigame-example.json
@@ -0,0 +1,157 @@
+{
+ "name": "Password Minigame Example",
+ "description": "Example scenario showing how to use the password minigame",
+ "rooms": {
+ "office": {
+ "id": "office",
+ "name": "Office",
+ "image": "room_office.png",
+ "map": "room_office.json",
+ "position": { "x": 0, "y": 0 },
+ "connections": []
+ }
+ },
+ "objects": [
+ {
+ "id": "secure_door",
+ "name": "Secure Door",
+ "type": "door",
+ "room": "office",
+ "x": 400,
+ "y": 300,
+ "image": "door.png",
+ "lockType": "password",
+ "requires": "admin123",
+ "passwordHint": "The default administrator password",
+ "showHint": true,
+ "showKeyboard": false,
+ "maxAttempts": 3,
+ "locked": true,
+ "observations": "A secure door with a password keypad."
+ },
+ {
+ "id": "computer_terminal",
+ "name": "Computer Terminal",
+ "type": "computer",
+ "room": "office",
+ "x": 200,
+ "y": 200,
+ "image": "computer.png",
+ "lockType": "password",
+ "requires": "cyber2024",
+ "passwordHint": "Current year with cyber prefix",
+ "showHint": true,
+ "showKeyboard": true,
+ "maxAttempts": 5,
+ "postitNote": "Password: cyber2024",
+ "showPostit": true,
+ "locked": true,
+ "observations": "A computer terminal requiring password access."
+ },
+ {
+ "id": "safe_box",
+ "name": "Safe Box",
+ "type": "container",
+ "room": "office",
+ "x": 600,
+ "y": 400,
+ "image": "safe.png",
+ "lockType": "password",
+ "requires": "treasure",
+ "showHint": false,
+ "showKeyboard": true,
+ "maxAttempts": 2,
+ "locked": true,
+ "contents": [
+ {
+ "id": "secret_document",
+ "name": "Secret Document",
+ "type": "document",
+ "image": "document.png",
+ "takeable": true,
+ "observations": "A classified document containing sensitive information."
+ }
+ ],
+ "observations": "A heavy safe box with a digital keypad."
+ },
+ {
+ "id": "office_computer",
+ "name": "Office Computer",
+ "type": "container",
+ "room": "office",
+ "x": 300,
+ "y": 300,
+ "image": "computer.png",
+ "lockType": "password",
+ "requires": "desktop123",
+ "showHint": false,
+ "showKeyboard": false,
+ "maxAttempts": 3,
+ "locked": true,
+ "contents": [
+ {
+ "id": "project_files",
+ "name": "Project Files",
+ "type": "document",
+ "takeable": true,
+ "observations": "Important project documentation."
+ },
+ {
+ "id": "meeting_notes",
+ "name": "Meeting Notes",
+ "type": "notes",
+ "takeable": true,
+ "readable": true,
+ "text": "Meeting notes about the upcoming project deadline..."
+ },
+ {
+ "id": "encryption_key",
+ "name": "Encryption Key",
+ "type": "key",
+ "takeable": true,
+ "observations": "A digital encryption key file."
+ }
+ ],
+ "observations": "An office computer with desktop access. Desktop mode will be automatically enabled."
+ },
+ {
+ "id": "tablet_device",
+ "name": "Executive Tablet",
+ "type": "container",
+ "room": "office",
+ "x": 500,
+ "y": 200,
+ "image": "tablet.png",
+ "lockType": "password",
+ "requires": "tablet2024",
+ "showHint": false,
+ "showKeyboard": true,
+ "maxAttempts": 3,
+ "locked": true,
+ "contents": [
+ {
+ "id": "executive_notes",
+ "name": "Executive Notes",
+ "type": "notes",
+ "takeable": true,
+ "readable": true,
+ "text": "Confidential executive meeting notes..."
+ },
+ {
+ "id": "financial_data",
+ "name": "Financial Data",
+ "type": "document",
+ "takeable": true,
+ "observations": "Sensitive financial information."
+ }
+ ],
+ "observations": "An executive tablet device. Desktop mode will be automatically enabled."
+ }
+ ],
+ "victoryConditions": [
+ {
+ "type": "collect_item",
+ "itemId": "secret_document"
+ }
+ ]
+}
diff --git a/scenarios/ceo_exfil.json b/scenarios/ceo_exfil.json
index 22517ca..3deeea9 100644
--- a/scenarios/ceo_exfil.json
+++ b/scenarios/ceo_exfil.json
@@ -30,8 +30,31 @@
"type": "pc",
"name": "Reception Computer",
"takeable": false,
+ "lockType": "password",
+ "passwordHint": "Optional hint text",
+ "showHint": true,
+ "showKeyboard": true,
+ "maxAttempts": 3,
+ "locked": true,
"requires": "password",
- "observations": "The reception's computer, currently locked"
+ "observations": "The reception's computer, currently locked",
+ "contents": [
+ {
+ "type": "notes",
+ "name": "Private Note",
+ "takeable": true,
+ "readable": true,
+ "text": "Closet keypad code: 7391 - Must move evidence to safe before audit",
+ "observations": "A hastily written note on expensive paper"
+ },
+ {
+ "type": "key",
+ "name": "Safe Key",
+ "takeable": true,
+ "key_id": "safe_key:52,29,44,37",
+ "observations": "A heavy-duty safe key hidden behind server equipment"
+ }
+ ]
},
{
"type": "tablet",
diff --git a/test-auto-desktop.html b/test-auto-desktop.html
new file mode 100644
index 0000000..bf9a83a
--- /dev/null
+++ b/test-auto-desktop.html
@@ -0,0 +1,383 @@
+
+
+
+
+
+ Auto Desktop Mode Test
+
+
+
+
+
+
+
+
+
+
+
+
Auto Desktop Mode Detection Test
+
Test automatic desktop mode detection for PC, tablet, and computer containers.
+
+
+ Auto-Detection Keywords: computer, pc, laptop, desktop, terminal, workstation, tablet, ipad, surface, monitor, screen, display, server, mainframe, console, kiosk, smartboard
+
+
+
+
+
Computer Container (Auto Desktop)
+
+ Test container with "computer" in the name - should automatically enable desktop mode.
+
+
+
+
+
+
+
Tablet Container (Auto Desktop)
+
+ Test container with "tablet" in the name - should automatically enable desktop mode.
+
+
+
+
+
+
+
PC Container (Auto Desktop)
+
+ Test container with "pc" in the name - should automatically enable desktop mode.
+
+
+
+
+
+
+
Regular Container (No Auto Desktop)
+
+ Test container with "safe" in the name - should NOT automatically enable desktop mode.
+
+
+
+
+
+
+
Manual Override Test
+
+ Test container with "safe" but manually force desktop mode - should enable desktop mode.
+
+
+
+
+
+
+
Test Results
+
Click a test button to see results here...
+
+
+
+
+
+
+
+
+
+
diff --git a/test-container-desktop.html b/test-container-desktop.html
new file mode 100644
index 0000000..503c8e2
--- /dev/null
+++ b/test-container-desktop.html
@@ -0,0 +1,348 @@
+
+
+
+
+
+ Container Desktop Mode Test
+
+
+
+
+
+
+
+
+
+
+
+
Container Desktop Mode Test Suite
+
Test the container minigame in desktop mode with desktop wallpaper and icons.
+
+
+
+
Standard Container Test
+
+ Test the standard container minigame (non-desktop mode) with various items.
+
+
+
+
+
+
+
Desktop Container Test
+
+ Test the container minigame in desktop mode with desktop wallpaper and scattered icons.
+
+
+
+
+
+
+
Desktop with Notes Test
+
+ Test desktop mode with notes that should trigger the notes minigame when clicked.
+
+
+
+
+
+
+
Empty Desktop Test
+
+ Test desktop mode with no items to see the empty desktop message.
+
+
+
+
+
+
+
Test Results
+
Click a test button to see results here...
+
+
+
+
+
+
+
+
+
+
diff --git a/test-container-simple.html b/test-container-simple.html
new file mode 100644
index 0000000..c307bf3
--- /dev/null
+++ b/test-container-simple.html
@@ -0,0 +1,189 @@
+
+
+
+
+
+ Simple Container Desktop Test
+
+
+
+
+
+
+
+
+
+
+
+
Simple Container Desktop Mode Test
+
Test the container desktop mode CSS and layout without the full framework.
+
+
+
+
Standard Container Layout
+
+ Standard container minigame layout (non-desktop mode).
+
+
+
+
+

+
+
Test Container
+
A standard container for testing
+
+
+
+
+
Contents
+
+
+

+
Test Key
+
+
+

+
Test Document
+
+
+
+
+
+
+
+
+
+
Desktop Container Layout
+
+ Container minigame in desktop mode with desktop wallpaper and scattered icons.
+
+
+
+
+
+
+
+

+
Secret Files
+
+
+

+
Encryption Key
+
+
+

+
Meeting Notes
+
+
+

+
Backup Files
+
+
+
+
+
+
+ Office Computer
+ Desktop with scattered files
+
+
+
+
+
+
+
+
+
+
+
+
+
Empty Desktop Layout
+
+ Desktop mode with no items to show the empty desktop message.
+
+
+
+
+
+
+
+ Empty Desktop
+ No files found
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-password-minigame.html b/test-password-minigame.html
new file mode 100644
index 0000000..3af199b
--- /dev/null
+++ b/test-password-minigame.html
@@ -0,0 +1,290 @@
+
+
+
+
+
+ Password Minigame Test
+
+
+
+
+
+
+
+
+
+
+
Password Minigame Test Suite
+
Test the new password minigame with various configurations and options.
+
+
+
+
Basic Password Test
+
+ Test basic password entry with show/hide functionality. Password: "secret123"
+
+
+
+
+
+
+
Password with Hint (Desktop Background)
+
+ Test password entry with hint button inside monitor bezel with desktop wallpaper. Password: "admin"
+ Hint: "The default administrator password"
+
+
+
+
+
+
+
Password with Onscreen Keyboard
+
+ Test password entry with onscreen QWERTY keyboard. Password: "keyboard"
+
+
+
+
+
+
+
Full Featured Password
+
+ Test password with all features: hint, keyboard, and custom attempts. Password: "fulltest"
+ Hint: "A complete test of all features"
+
+
+
+
+
+
+
Password with Post-it Note
+
+ Test password with post-it note on desktop. Password: "postit"
+ Post-it: "Password is written on the sticky note!"
+
+
+
+
+
+
+
Wrong Password Test
+
+ Test entering wrong passwords to trigger failure scenarios. Try: "wrong", "incorrect", "fail"
+
+
+
+
+
+
+
Test Results
+
Click a test button to see results here...
+
+
+
+
+
+
+
diff --git a/verify-css.html b/verify-css.html
new file mode 100644
index 0000000..35dd20b
--- /dev/null
+++ b/verify-css.html
@@ -0,0 +1,208 @@
+
+
+
+
+
+ CSS Verification
+
+
+
+
+
+
+
+
+
+
+
CSS Verification Test
+
This page verifies that the password minigame CSS is properly loaded and applied.
+
+
+
Password Field with Monitor Bezel Test
+
+
CSS NOT LOADED
+
+
+
+
Hint System Test
+
+
+
+ Hint: This is a test hint
+
+
+
CSS NOT LOADED
+
+
+
+
Onscreen Keyboard Test
+
+
CSS NOT LOADED
+
+
+
+
Action Buttons Test
+
+
+
+
+
CSS NOT LOADED
+
+
+
+
Attempts Counter Test
+
+ Attempts: 0/3
+
+
CSS NOT LOADED
+
+
+
+
Overall Status
+
VERIFICATION IN PROGRESS
+
+
+
+
+
+