mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
Enhance Minigame Framework: Add new Text File Minigame and integrate it into the existing system. Update interaction logic to support text file objects, allowing players to view and interact with file contents. Introduce CSS styles for the text file interface and update relevant HTML files to include new styles. Modify existing minigames to ensure compatibility with the new text file functionality, enhancing overall gameplay experience.
This commit is contained in:
BIN
assets/objects/text_file.png
Normal file
BIN
assets/objects/text_file.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 173 B |
@@ -15,11 +15,105 @@
|
||||
background: #000;
|
||||
}
|
||||
|
||||
/* Monitor bezel for desktop containers */
|
||||
.container-monitor-bezel {
|
||||
background: #666;
|
||||
border: 8px solid #444;
|
||||
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;
|
||||
margin: 20px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container-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;
|
||||
}
|
||||
|
||||
.container-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;
|
||||
}
|
||||
|
||||
/* Post-it notes for container monitor bezel */
|
||||
.container-monitor-bezel .postit-note {
|
||||
position: absolute;
|
||||
bottom: -15px;
|
||||
left: 20px;
|
||||
z-index: 15;
|
||||
margin: 0;
|
||||
transform: rotate(-3deg);
|
||||
background: #ffff88;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
padding: 15px;
|
||||
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3);
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 8px;
|
||||
color: #333;
|
||||
max-width: 200px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.container-monitor-bezel .postit-note::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 15px solid transparent;
|
||||
border-top: 15px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.container-monitor-bezel .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;
|
||||
}
|
||||
|
||||
.container-monitor-bezel .postit-note:nth-child(2) {
|
||||
left: 120px;
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
|
||||
.container-monitor-bezel .postit-note:nth-child(3) {
|
||||
left: 220px;
|
||||
transform: rotate(-1deg);
|
||||
}
|
||||
|
||||
.desktop-background {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.desktop-wallpaper {
|
||||
@@ -28,6 +122,8 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('../assets/mini-games/desktop-wallpaper.png');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
@@ -35,18 +131,9 @@
|
||||
image-rendering: pixelated;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: crisp-edges;
|
||||
opacity: 0.8;
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5) inset;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.minigame-message-container {
|
||||
@@ -191,384 +190,4 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
448
css/password-minigame.css
Normal file
448
css/password-minigame.css
Normal file
@@ -0,0 +1,448 @@
|
||||
/* 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: #666;
|
||||
border: 8px solid #444;
|
||||
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);
|
||||
}
|
||||
|
||||
.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: 180px;
|
||||
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;
|
||||
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;
|
||||
top: -40px;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
/* Post-it notes stuck to monitor bezel */
|
||||
.monitor-bezel .postit-note {
|
||||
bottom: -15px;
|
||||
left: 20px;
|
||||
z-index: 15;
|
||||
margin: 0;
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
|
||||
/* Post-it notes between monitor-bezel and keyboard */
|
||||
.password-minigame-area .postit-note {
|
||||
position: relative;
|
||||
margin: 15px 20px;
|
||||
z-index: 15;
|
||||
transform: rotate(-3deg);
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.password-minigame-area .postit-note:nth-child(2) {
|
||||
margin-left: 140px;
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
|
||||
.password-minigame-area .postit-note:nth-child(3) {
|
||||
margin-left: 260px;
|
||||
transform: rotate(-1deg);
|
||||
}
|
||||
|
||||
.monitor-bezel .postit-note:nth-child(2) {
|
||||
left: 120px;
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
|
||||
.monitor-bezel .postit-note:nth-child(3) {
|
||||
left: 220px;
|
||||
transform: rotate(-1deg);
|
||||
}
|
||||
|
||||
.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;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.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;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.key-shift {
|
||||
background: #e67e22;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.key-shift:hover {
|
||||
background: #d35400;
|
||||
}
|
||||
|
||||
.key-shift.active {
|
||||
background: #f39c12;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.password-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.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;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.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;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.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;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
433
css/text-file-minigame.css
Normal file
433
css/text-file-minigame.css
Normal file
@@ -0,0 +1,433 @@
|
||||
/* Text File Minigame Styles */
|
||||
|
||||
/* Import VT font */
|
||||
@import url('https://fonts.googleapis.com/css2?family=VT323:wght@400&display=swap');
|
||||
|
||||
/* Text File Minigame Container */
|
||||
.text-file-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #d1d5db;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
font-family: 'VT323', monospace;
|
||||
}
|
||||
|
||||
/* Mac-style Window Title Bar */
|
||||
.text-file-window-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: linear-gradient(to bottom, #f6f6f6 0%, #e8e8e8 100%);
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
border-radius: 12px 12px 0 0;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.window-control {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.window-control.close {
|
||||
background: #ff5f57;
|
||||
}
|
||||
|
||||
.window-control.minimize {
|
||||
background: #ffbd2e;
|
||||
}
|
||||
|
||||
.window-control.maximize {
|
||||
background: #28ca42;
|
||||
}
|
||||
|
||||
.window-control:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.window-title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
/* File Header Section */
|
||||
.file-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #212529;
|
||||
margin-bottom: 4px;
|
||||
font-family: 'VT323', monospace;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
font-family: 'VT323', monospace;
|
||||
}
|
||||
|
||||
.file-type {
|
||||
background: #e9ecef;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dee2e6;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* File Content Area */
|
||||
.file-content-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.content-label {
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
font-weight: 500;
|
||||
font-family: 'VT323', monospace;
|
||||
}
|
||||
|
||||
.content-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: #ffffff;
|
||||
border: 1px solid #d1d5db;
|
||||
color: #374151;
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
font-family: 'VT323', monospace;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: #f3f4f6;
|
||||
border-color: #9ca3af;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
background: #e5e7eb;
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* File Content Display */
|
||||
.file-content {
|
||||
flex: 1;
|
||||
padding: 16px 20px;
|
||||
overflow: auto;
|
||||
background: #ffffff;
|
||||
border: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.file-text {
|
||||
color: #000000;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
}
|
||||
|
||||
/* File Observations Section */
|
||||
.file-observations {
|
||||
margin: 0;
|
||||
padding: 16px 20px;
|
||||
background: #fff3cd;
|
||||
border-top: 1px solid #ffeaa7;
|
||||
}
|
||||
|
||||
.file-observations h4 {
|
||||
color: #856404;
|
||||
font-size: 14px;
|
||||
margin: 0 0 8px 0;
|
||||
font-family: 'VT323', monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.file-observations p {
|
||||
color: #6c5700;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
font-family: 'VT323', monospace;
|
||||
}
|
||||
|
||||
/* Text Selection Styling */
|
||||
.file-content ::selection {
|
||||
background: #3b82f6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.file-content ::-moz-selection {
|
||||
background: #3b82f6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar Styling */
|
||||
.file-content::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.file-content::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.file-content::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 6px;
|
||||
border: 2px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.file-content::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
.file-content::-webkit-scrollbar-corner {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.text-file-container {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.text-file-window-header {
|
||||
border-radius: 8px 8px 0 0;
|
||||
padding: 6px 10px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.window-control {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
font-size: 12px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.file-header {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
font-size: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
padding: 6px 12px;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.content-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 3px 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.file-content {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.file-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.file-observations {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.file-observations h4 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.file-observations p {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark Mode Support (Optional) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.text-file-container {
|
||||
background: #1f2937;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
.text-file-window-header {
|
||||
background: linear-gradient(to bottom, #374151 0%, #1f2937 100%);
|
||||
border-bottom-color: #374151;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.file-header {
|
||||
background: #374151;
|
||||
border-bottom-color: #4b5563;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.file-type {
|
||||
background: #4b5563;
|
||||
border-color: #6b7280;
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
background: #374151;
|
||||
border-bottom-color: #4b5563;
|
||||
}
|
||||
|
||||
.content-label {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: #4b5563;
|
||||
border-color: #6b7280;
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: #6b7280;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
.file-content {
|
||||
background: #1f2937;
|
||||
}
|
||||
|
||||
.file-text {
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.file-observations {
|
||||
background: #451a03;
|
||||
border-top-color: #92400e;
|
||||
}
|
||||
|
||||
.file-observations h4 {
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.file-observations p {
|
||||
color: #fcd34d;
|
||||
}
|
||||
|
||||
.file-content::-webkit-scrollbar-track {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
.file-content::-webkit-scrollbar-thumb {
|
||||
background: #6b7280;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
.file-content::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.file-content::-webkit-scrollbar-corner {
|
||||
background: #374151;
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,8 @@
|
||||
<link rel="stylesheet" href="css/phone.css">
|
||||
<link rel="stylesheet" href="css/pin.css">
|
||||
<link rel="stylesheet" href="css/minigames.css">
|
||||
<link rel="stylesheet" href="css/password-minigame.css">
|
||||
<link rel="stylesheet" href="css/text-file-minigame.css">
|
||||
|
||||
<!-- External JavaScript libraries -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
|
||||
|
||||
@@ -92,12 +92,18 @@ export class ContainerMinigame extends MinigameScene {
|
||||
createDesktopUI() {
|
||||
this.gameContainer.innerHTML = `
|
||||
<div class="container-minigame desktop-mode">
|
||||
<div class="desktop-background">
|
||||
<div class="desktop-wallpaper"></div>
|
||||
<div class="desktop-icons" id="desktop-icons">
|
||||
<!-- Desktop icons will be populated here -->
|
||||
<div class="container-monitor-bezel">
|
||||
<div class="desktop-background">
|
||||
<div class="desktop-wallpaper"></div>
|
||||
<div class="desktop-icons" id="desktop-icons">
|
||||
<!-- Desktop icons will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
${this.containerItem.scenarioData.postitNote && this.containerItem.scenarioData.showPostit ? `
|
||||
<div class="postit-note">${this.containerItem.scenarioData.postitNote}</div>
|
||||
` : ''}
|
||||
|
||||
<div class="desktop-taskbar">
|
||||
<div class="desktop-info">
|
||||
@@ -145,16 +151,15 @@ export class ContainerMinigame extends MinigameScene {
|
||||
itemImg.name = item.type;
|
||||
itemImg.objectId = `container_${index}`;
|
||||
|
||||
// Add click handler for taking items
|
||||
if (item.takeable) {
|
||||
itemImg.style.cursor = 'pointer';
|
||||
|
||||
// Special handling for notes - trigger notes minigame instead of taking
|
||||
if (item.type === 'notes' && item.readable && item.text) {
|
||||
itemImg.addEventListener('click', () => this.handleNotesItem(item, itemImg));
|
||||
} else {
|
||||
itemImg.addEventListener('click', () => this.takeItem(item, itemImg));
|
||||
}
|
||||
// Add click handler for all items (both takeable and interactive)
|
||||
itemImg.style.cursor = 'pointer';
|
||||
|
||||
// Check if this is an interactive item that should trigger a minigame
|
||||
if (this.isInteractiveItem(item)) {
|
||||
itemImg.addEventListener('click', () => this.handleInteractiveItem(item, itemImg));
|
||||
} else if (item.takeable) {
|
||||
// Regular takeable items
|
||||
itemImg.addEventListener('click', () => this.takeItem(item, itemImg));
|
||||
}
|
||||
|
||||
// Create tooltip
|
||||
@@ -195,16 +200,15 @@ export class ContainerMinigame extends MinigameScene {
|
||||
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));
|
||||
}
|
||||
// Add click handler for all items (both takeable and interactive)
|
||||
icon.style.cursor = 'pointer';
|
||||
|
||||
// Check if this is an interactive item that should trigger a minigame
|
||||
if (this.isInteractiveItem(item)) {
|
||||
icon.addEventListener('click', () => this.handleInteractiveItem(item, iconImg));
|
||||
} else if (item.takeable) {
|
||||
// Regular takeable items
|
||||
icon.addEventListener('click', () => this.takeItem(item, iconImg));
|
||||
}
|
||||
|
||||
// Position icon randomly on desktop
|
||||
@@ -233,30 +237,37 @@ export class ContainerMinigame extends MinigameScene {
|
||||
}
|
||||
}
|
||||
|
||||
handleNotesItem(item, itemElement) {
|
||||
console.log('Handling notes item from container:', item);
|
||||
isInteractiveItem(item) {
|
||||
// Check if this item should trigger a minigame instead of being taken
|
||||
|
||||
// Remove the note from container display
|
||||
itemElement.parentElement.remove();
|
||||
|
||||
// Remove from contents array
|
||||
const itemIndex = this.contents.findIndex(content => content === item);
|
||||
if (itemIndex !== -1) {
|
||||
this.contents.splice(itemIndex, 1);
|
||||
// Notes with readable text
|
||||
if (item.type === 'notes' && item.readable && item.text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
this.showMessage(`Read ${item.name}`, 'success');
|
||||
|
||||
// If container is now empty, update display
|
||||
if (this.contents.length === 0) {
|
||||
const contentsGrid = document.getElementById('container-contents-grid');
|
||||
if (contentsGrid) {
|
||||
contentsGrid.innerHTML = '<p class="empty-contents">This container is empty.</p>';
|
||||
}
|
||||
// Text files
|
||||
if (item.type === 'text_file' && item.text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Store container state for return after notes minigame
|
||||
// Phone with messages
|
||||
if (item.type === 'phone' && (item.text || item.voice)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Workstation (crypto workstation)
|
||||
if (item.type === 'workstation') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add more interactive item types as needed
|
||||
return false;
|
||||
}
|
||||
|
||||
handleInteractiveItem(item, itemElement) {
|
||||
console.log('Handling interactive item from container:', item);
|
||||
|
||||
// Store container state for return after minigame
|
||||
const containerState = {
|
||||
containerItem: this.containerItem,
|
||||
contents: this.contents,
|
||||
@@ -269,6 +280,24 @@ export class ContainerMinigame extends MinigameScene {
|
||||
// Close the container minigame first
|
||||
this.complete(false);
|
||||
|
||||
// Route to appropriate minigame based on item type
|
||||
if (item.type === 'notes' && item.readable && item.text) {
|
||||
this.handleNotesItem(item);
|
||||
} else if (item.type === 'text_file' && item.text) {
|
||||
this.handleTextFileItem(item);
|
||||
} else if (item.type === 'phone' && (item.text || item.voice)) {
|
||||
this.handlePhoneItem(item);
|
||||
} else if (item.type === 'workstation') {
|
||||
this.handleWorkstationItem(item);
|
||||
} else {
|
||||
console.warn('Unknown interactive item type:', item.type);
|
||||
this.showMessage(`Unknown item type: ${item.type}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
handleNotesItem(item) {
|
||||
console.log('Handling notes item from container:', item);
|
||||
|
||||
// Start the notes minigame
|
||||
if (window.startNotesMinigame) {
|
||||
// Create a temporary sprite-like object for the notes minigame
|
||||
@@ -286,6 +315,88 @@ export class ContainerMinigame extends MinigameScene {
|
||||
}
|
||||
}
|
||||
|
||||
handleTextFileItem(item) {
|
||||
console.log('Handling text file item from container:', item);
|
||||
|
||||
// Start the text file minigame
|
||||
if (window.MinigameFramework) {
|
||||
const minigameParams = {
|
||||
title: `Text File - ${item.name || 'Unknown File'}`,
|
||||
fileName: item.name || 'Unknown File',
|
||||
fileContent: item.text,
|
||||
fileType: item.fileType || 'text',
|
||||
observations: item.observations,
|
||||
source: item.source || 'Container',
|
||||
onComplete: (success, result) => {
|
||||
console.log('Text file minigame completed:', success, result);
|
||||
}
|
||||
};
|
||||
|
||||
window.MinigameFramework.startMinigame('text-file', null, minigameParams);
|
||||
} else {
|
||||
console.error('MinigameFramework not available');
|
||||
window.gameAlert('Text file minigame not available', 'error', 'Error', 3000);
|
||||
}
|
||||
}
|
||||
|
||||
handlePhoneItem(item) {
|
||||
console.log('Handling phone item from container:', item);
|
||||
|
||||
// Start the phone messages minigame
|
||||
if (window.MinigameFramework) {
|
||||
const messages = [];
|
||||
|
||||
// Add text message if available
|
||||
if (item.text) {
|
||||
messages.push({
|
||||
type: 'text',
|
||||
sender: item.sender || 'Unknown',
|
||||
text: item.text,
|
||||
timestamp: item.timestamp || 'Unknown time',
|
||||
read: false
|
||||
});
|
||||
}
|
||||
|
||||
// Add voice message if available
|
||||
if (item.voice) {
|
||||
messages.push({
|
||||
type: 'voice',
|
||||
sender: item.sender || 'Unknown',
|
||||
text: item.text || null,
|
||||
voice: item.voice,
|
||||
timestamp: item.timestamp || 'Unknown time',
|
||||
read: false
|
||||
});
|
||||
}
|
||||
|
||||
const minigameParams = {
|
||||
title: item.name || 'Phone Messages',
|
||||
messages: messages,
|
||||
observations: item.observations,
|
||||
onComplete: (success, result) => {
|
||||
console.log('Phone messages minigame completed:', success, result);
|
||||
}
|
||||
};
|
||||
|
||||
window.MinigameFramework.startMinigame('phone-messages', null, minigameParams);
|
||||
} else {
|
||||
console.error('MinigameFramework not available');
|
||||
window.gameAlert('Phone minigame not available', 'error', 'Error', 3000);
|
||||
}
|
||||
}
|
||||
|
||||
handleWorkstationItem(item) {
|
||||
console.log('Handling workstation item from container:', item);
|
||||
|
||||
// Open the crypto workstation
|
||||
if (window.openCryptoWorkstation) {
|
||||
window.openCryptoWorkstation();
|
||||
} else {
|
||||
console.error('Crypto workstation not available');
|
||||
window.gameAlert('Crypto workstation not available', 'error', 'Error', 3000);
|
||||
}
|
||||
}
|
||||
|
||||
takeItem(item, itemElement) {
|
||||
console.log('Taking item from container:', item);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ export { ContainerMinigame, startContainerMinigame, returnToContainerAfterNotes
|
||||
export { PhoneMessagesMinigame, returnToPhoneAfterNotes } from './phone/phone-messages-minigame.js';
|
||||
export { PinMinigame, startPinMinigame } from './pin/pin-minigame.js';
|
||||
export { PasswordMinigame } from './password/password-minigame.js';
|
||||
export { TextFileMinigame, returnToTextFileAfterNotes } from './text-file/text-file-minigame.js';
|
||||
|
||||
// Initialize the global minigame framework for backward compatibility
|
||||
import { MinigameFramework } from './framework/minigame-manager.js';
|
||||
@@ -65,6 +66,9 @@ import { PinMinigame, startPinMinigame } from './pin/pin-minigame.js';
|
||||
// Import the password minigame
|
||||
import { PasswordMinigame } from './password/password-minigame.js';
|
||||
|
||||
// Import the text file minigame
|
||||
import { TextFileMinigame, returnToTextFileAfterNotes } from './text-file/text-file-minigame.js';
|
||||
|
||||
// Register minigames
|
||||
MinigameFramework.registerScene('lockpicking', LockpickingMinigamePhaser); // Use Phaser version as default
|
||||
MinigameFramework.registerScene('lockpicking-phaser', LockpickingMinigamePhaser); // Keep explicit phaser name
|
||||
@@ -77,6 +81,7 @@ MinigameFramework.registerScene('container', ContainerMinigame);
|
||||
MinigameFramework.registerScene('phone-messages', PhoneMessagesMinigame);
|
||||
MinigameFramework.registerScene('pin', PinMinigame);
|
||||
MinigameFramework.registerScene('password', PasswordMinigame);
|
||||
MinigameFramework.registerScene('text-file', TextFileMinigame);
|
||||
|
||||
// Make minigame functions available globally
|
||||
window.startNotesMinigame = startNotesMinigame;
|
||||
@@ -87,4 +92,5 @@ window.startLockpickSetMinigame = startLockpickSetMinigame;
|
||||
window.startContainerMinigame = startContainerMinigame;
|
||||
window.returnToContainerAfterNotes = returnToContainerAfterNotes;
|
||||
window.returnToPhoneAfterNotes = returnToPhoneAfterNotes;
|
||||
window.returnToTextFileAfterNotes = returnToTextFileAfterNotes;
|
||||
window.startPinMinigame = startPinMinigame;
|
||||
@@ -748,6 +748,15 @@ export function startNotesMinigame(item, noteContent, observationText, navigateT
|
||||
window.returnToPhoneAfterNotes();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Check if we need to return to text file after notes minigame
|
||||
if (window.pendingTextFileReturn && window.returnToTextFileAfterNotes) {
|
||||
console.log('Returning to text file after notes minigame');
|
||||
// Small delay to ensure notes minigame cleanup completes
|
||||
setTimeout(() => {
|
||||
window.returnToTextFileAfterNotes();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ export class PasswordMinigame extends MinigameScene {
|
||||
attempts: 0,
|
||||
showPassword: false,
|
||||
postitNote: params.postitNote || '',
|
||||
showPostit: params.showPostit || false
|
||||
showPostit: params.showPostit || false,
|
||||
capsLock: false
|
||||
};
|
||||
|
||||
// Store the correct password for validation
|
||||
@@ -42,12 +43,6 @@ export class PasswordMinigame extends MinigameScene {
|
||||
// Create the password entry interface
|
||||
this.gameContainer.innerHTML = `
|
||||
<div class="password-minigame-area">
|
||||
${this.gameData.showPostit && this.gameData.postitNote ? `
|
||||
<div class="postit-note">
|
||||
${this.gameData.postitNote}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="monitor-bezel">
|
||||
<div class="monitor-screen">
|
||||
<div class="password-input-container">
|
||||
@@ -75,6 +70,12 @@ export class PasswordMinigame extends MinigameScene {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.gameData.showPostit && this.gameData.postitNote ? `
|
||||
<div class="postit-note">
|
||||
${this.gameData.postitNote}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${this.gameData.showKeyboard ? `
|
||||
<div class="onscreen-keyboard" id="onscreen-keyboard">
|
||||
<div class="keyboard-row">
|
||||
@@ -114,6 +115,7 @@ export class PasswordMinigame extends MinigameScene {
|
||||
<button class="key" data-key="l">L</button>
|
||||
</div>
|
||||
<div class="keyboard-row">
|
||||
<button class="key key-shift" data-key="Shift" id="shift-key">Shift</button>
|
||||
<button class="key" data-key="z">Z</button>
|
||||
<button class="key" data-key="x">X</button>
|
||||
<button class="key" data-key="c">C</button>
|
||||
@@ -268,6 +270,8 @@ export class PasswordMinigame extends MinigameScene {
|
||||
this.submitPassword();
|
||||
} else if (keyValue === 'Escape') {
|
||||
this.cancelPassword();
|
||||
} else if (keyValue === 'Shift') {
|
||||
this.toggleCapsLock();
|
||||
} else if (keyValue === 'Backspace') {
|
||||
this.passwordField.value = this.passwordField.value.slice(0, -1);
|
||||
this.gameData.password = this.passwordField.value;
|
||||
@@ -275,7 +279,8 @@ export class PasswordMinigame extends MinigameScene {
|
||||
this.passwordField.value += ' ';
|
||||
this.gameData.password = this.passwordField.value;
|
||||
} else if (keyValue && keyValue.length === 1) {
|
||||
this.passwordField.value += keyValue;
|
||||
const char = this.gameData.capsLock ? keyValue.toUpperCase() : keyValue.toLowerCase();
|
||||
this.passwordField.value += char;
|
||||
this.gameData.password = this.passwordField.value;
|
||||
}
|
||||
|
||||
@@ -283,6 +288,18 @@ export class PasswordMinigame extends MinigameScene {
|
||||
this.passwordField.focus();
|
||||
}
|
||||
|
||||
toggleCapsLock() {
|
||||
this.gameData.capsLock = !this.gameData.capsLock;
|
||||
const shiftKey = document.getElementById('shift-key');
|
||||
if (shiftKey) {
|
||||
if (this.gameData.capsLock) {
|
||||
shiftKey.classList.add('active');
|
||||
} else {
|
||||
shiftKey.classList.remove('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submitPassword() {
|
||||
if (!this.gameState.isActive) return;
|
||||
|
||||
|
||||
377
js/minigames/text-file/text-file-minigame.js
Normal file
377
js/minigames/text-file/text-file-minigame.js
Normal file
@@ -0,0 +1,377 @@
|
||||
import { MinigameScene } from '../framework/base-minigame.js';
|
||||
|
||||
export class TextFileMinigame extends MinigameScene {
|
||||
constructor(container, params) {
|
||||
super(container, params);
|
||||
|
||||
// Ensure params is an object with default values
|
||||
const safeParams = params || {};
|
||||
|
||||
// Initialize text file specific state
|
||||
this.textFileData = {
|
||||
fileName: safeParams.fileName || 'Unknown File',
|
||||
fileContent: safeParams.fileContent || '',
|
||||
fileType: safeParams.fileType || 'text',
|
||||
observations: safeParams.observations || '',
|
||||
source: safeParams.source || 'Unknown Source'
|
||||
};
|
||||
}
|
||||
|
||||
init() {
|
||||
// Call parent init to set up basic UI structure
|
||||
super.init();
|
||||
|
||||
// Customize the header
|
||||
this.headerElement.innerHTML = `
|
||||
<h3>📄 ${this.textFileData.fileName}</h3>
|
||||
<p>Viewing text file contents</p>
|
||||
`;
|
||||
|
||||
// Add notebook button to minigame controls (before cancel button)
|
||||
if (this.controlsElement) {
|
||||
const notebookBtn = document.createElement('button');
|
||||
notebookBtn.className = 'minigame-button';
|
||||
notebookBtn.id = 'minigame-notebook';
|
||||
notebookBtn.innerHTML = '📝 Add to Notebook';
|
||||
this.controlsElement.appendChild(notebookBtn);
|
||||
|
||||
// Change cancel button text to "Close"
|
||||
const cancelBtn = document.getElementById('minigame-cancel');
|
||||
if (cancelBtn) {
|
||||
cancelBtn.innerHTML = 'Close';
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the text file interface
|
||||
this.setupTextFileInterface();
|
||||
|
||||
// Set up event listeners
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
setupTextFileInterface() {
|
||||
// Create the text file interface with Mac-style window
|
||||
this.gameContainer.innerHTML = `
|
||||
<div class="text-file-container">
|
||||
<div class="text-file-window-header">
|
||||
<div class="window-controls">
|
||||
<button class="window-control close" title="Close"></button>
|
||||
<button class="window-control minimize" title="Minimize"></button>
|
||||
<button class="window-control maximize" title="Maximize"></button>
|
||||
</div>
|
||||
<div class="window-title">${this.textFileData.fileName}</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="file-header">
|
||||
<div class="file-icon">📄</div>
|
||||
<div class="file-info">
|
||||
<div class="file-name">${this.textFileData.fileName}</div>
|
||||
<div class="file-meta">
|
||||
<span class="file-type">${this.textFileData.fileType.toUpperCase()}</span>
|
||||
<span class="file-size">${this.getFileSize()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="file-content-area">
|
||||
<div class="content-header">
|
||||
<span class="content-label">File Contents:</span>
|
||||
<div class="content-actions">
|
||||
<button class="action-btn" id="copy-btn" title="Copy to clipboard">📋 Copy</button>
|
||||
<button class="action-btn" id="select-all-btn" title="Select all text">Select All</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-content" id="file-content">
|
||||
${this.formatFileContent()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.textFileData.observations ? `
|
||||
<div class="file-observations">
|
||||
<h4>📋 Observations:</h4>
|
||||
<p>${this.textFileData.observations}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Get references to important elements
|
||||
this.fileContent = document.getElementById('file-content');
|
||||
this.copyBtn = document.getElementById('copy-btn');
|
||||
this.selectAllBtn = document.getElementById('select-all-btn');
|
||||
|
||||
// Get window control references
|
||||
this.closeBtn = this.gameContainer.querySelector('.window-control.close');
|
||||
this.minimizeBtn = this.gameContainer.querySelector('.window-control.minimize');
|
||||
this.maximizeBtn = this.gameContainer.querySelector('.window-control.maximize');
|
||||
}
|
||||
|
||||
formatFileContent() {
|
||||
// Format the file content for display
|
||||
let content = this.textFileData.fileContent;
|
||||
|
||||
// Escape HTML characters
|
||||
content = content.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// Convert line breaks to <br> tags
|
||||
content = content.replace(/\n/g, '<br>');
|
||||
|
||||
// Wrap in a pre element to preserve formatting
|
||||
return `<pre class="file-text">${content}</pre>`;
|
||||
}
|
||||
|
||||
getFileSize() {
|
||||
// Calculate approximate file size
|
||||
const bytes = new Blob([this.textFileData.fileContent]).size;
|
||||
if (bytes < 1024) {
|
||||
return `${bytes} B`;
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
} else {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Window controls
|
||||
this.addEventListener(this.closeBtn, 'click', () => {
|
||||
this.complete(false);
|
||||
});
|
||||
|
||||
this.addEventListener(this.minimizeBtn, 'click', () => {
|
||||
// For now, just show a message (could implement minimize functionality later)
|
||||
this.showSuccess("Minimize functionality not implemented", false, 2000);
|
||||
});
|
||||
|
||||
this.addEventListener(this.maximizeBtn, 'click', () => {
|
||||
// For now, just show a message (could implement maximize functionality later)
|
||||
this.showSuccess("Maximize functionality not implemented", false, 2000);
|
||||
});
|
||||
|
||||
// Copy button
|
||||
this.addEventListener(this.copyBtn, 'click', () => {
|
||||
this.copyToClipboard();
|
||||
});
|
||||
|
||||
// Select all button
|
||||
this.addEventListener(this.selectAllBtn, 'click', () => {
|
||||
this.selectAllText();
|
||||
});
|
||||
|
||||
// Notebook button (in minigame controls)
|
||||
const notebookBtn = document.getElementById('minigame-notebook');
|
||||
if (notebookBtn) {
|
||||
this.addEventListener(notebookBtn, 'click', () => {
|
||||
this.addToNotebook();
|
||||
});
|
||||
}
|
||||
|
||||
// Keyboard controls
|
||||
this.addEventListener(document, 'keydown', (event) => {
|
||||
this.handleKeyPress(event);
|
||||
});
|
||||
|
||||
// Double-click to select all
|
||||
this.addEventListener(this.fileContent, 'dblclick', () => {
|
||||
this.selectAllText();
|
||||
});
|
||||
}
|
||||
|
||||
handleKeyPress(event) {
|
||||
if (!this.gameState.isActive) return;
|
||||
|
||||
// Handle Ctrl+A for select all
|
||||
if (event.ctrlKey && event.key === 'a') {
|
||||
event.preventDefault();
|
||||
this.selectAllText();
|
||||
}
|
||||
|
||||
// Handle Ctrl+C for copy (when text is selected)
|
||||
if (event.ctrlKey && event.key === 'c') {
|
||||
// Let the default behavior handle copying selected text
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Escape to close
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
this.complete(false);
|
||||
}
|
||||
}
|
||||
|
||||
copyToClipboard() {
|
||||
try {
|
||||
// Use the modern clipboard API if available
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(this.textFileData.fileContent).then(() => {
|
||||
this.showSuccess("File content copied to clipboard!", false, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy to clipboard:', err);
|
||||
this.fallbackCopyToClipboard();
|
||||
});
|
||||
} else {
|
||||
// Fallback for older browsers or non-secure contexts
|
||||
this.fallbackCopyToClipboard();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Copy failed:', error);
|
||||
this.fallbackCopyToClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
fallbackCopyToClipboard() {
|
||||
// Create a temporary textarea element
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = this.textFileData.fileContent;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
textArea.style.top = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
|
||||
try {
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
const successful = document.execCommand('copy');
|
||||
|
||||
if (successful) {
|
||||
this.showSuccess("File content copied to clipboard!", false, 2000);
|
||||
} else {
|
||||
this.showFailure("Failed to copy to clipboard", false, 2000);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Fallback copy failed:', err);
|
||||
this.showFailure("Copy not supported on this browser", false, 2000);
|
||||
} finally {
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
}
|
||||
|
||||
selectAllText() {
|
||||
// Select all text in the file content
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(this.fileContent);
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
this.showSuccess("All text selected", false, 1000);
|
||||
}
|
||||
|
||||
addToNotebook() {
|
||||
// Check if there's content to add
|
||||
if (!this.textFileData.fileContent || this.textFileData.fileContent.trim() === '') {
|
||||
this.showFailure("No content to add to notebook", false, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create comprehensive notebook content
|
||||
const notebookContent = this.formatContentForNotebook();
|
||||
const notebookTitle = `Text File - ${this.textFileData.fileName}`;
|
||||
const notebookObservations = this.textFileData.observations ||
|
||||
`Text file "${this.textFileData.fileName}" from ${this.textFileData.source}`;
|
||||
|
||||
// Check if notes minigame is available
|
||||
if (window.startNotesMinigame) {
|
||||
// Store the text file state globally so we can return to it
|
||||
const textFileState = {
|
||||
fileName: this.textFileData.fileName,
|
||||
fileContent: this.textFileData.fileContent,
|
||||
fileType: this.textFileData.fileType,
|
||||
observations: this.textFileData.observations,
|
||||
source: this.textFileData.source,
|
||||
params: this.params
|
||||
};
|
||||
|
||||
window.pendingTextFileReturn = textFileState;
|
||||
|
||||
// Create a text file item for the notes minigame
|
||||
const textFileItem = {
|
||||
scenarioData: {
|
||||
type: 'text_file',
|
||||
name: notebookTitle,
|
||||
text: notebookContent,
|
||||
observations: notebookObservations,
|
||||
important: true // Mark as important since it's from a file
|
||||
}
|
||||
};
|
||||
|
||||
// Start notes minigame - it will handle returning to text file via returnToTextFileAfterNotes
|
||||
window.startNotesMinigame(
|
||||
textFileItem,
|
||||
notebookContent,
|
||||
notebookObservations,
|
||||
null, // Let notes minigame auto-navigate to the newly added note
|
||||
false, // Don't auto-add to inventory
|
||||
false // Don't auto-close
|
||||
);
|
||||
|
||||
this.showSuccess("Added file content to notebook", false, 2000);
|
||||
} else {
|
||||
this.showFailure("Notebook not available", false, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
formatContentForNotebook() {
|
||||
let content = `Text File: ${this.textFileData.fileName}\n`;
|
||||
content += `Source: ${this.textFileData.source}\n`;
|
||||
content += `Type: ${this.textFileData.fileType.toUpperCase()}\n`;
|
||||
content += `Date: ${new Date().toLocaleString()}\n\n`;
|
||||
content += `${'='.repeat(50)}\n\n`;
|
||||
content += `FILE CONTENTS:\n`;
|
||||
content += `${'-'.repeat(20)}\n\n`;
|
||||
content += this.textFileData.fileContent;
|
||||
content += `\n\n${'='.repeat(50)}\n`;
|
||||
content += `End of File: ${this.textFileData.fileName}`;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
start() {
|
||||
// Call parent start
|
||||
super.start();
|
||||
|
||||
console.log("Text file minigame started");
|
||||
console.log("File:", this.textFileData.fileName);
|
||||
console.log("Content length:", this.textFileData.fileContent.length);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
// Call parent cleanup (handles event listeners)
|
||||
super.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to return to text file after notes minigame (similar to container pattern)
|
||||
export function returnToTextFileAfterNotes() {
|
||||
console.log('Returning to text file after notes minigame');
|
||||
|
||||
// Check if there's a pending text file return
|
||||
if (window.pendingTextFileReturn) {
|
||||
const textFileState = window.pendingTextFileReturn;
|
||||
|
||||
// Clear the pending return state
|
||||
window.pendingTextFileReturn = null;
|
||||
|
||||
// Start the text file minigame with the stored state
|
||||
if (window.MinigameFramework) {
|
||||
window.MinigameFramework.startMinigame('text-file', null, {
|
||||
title: `Text File - ${textFileState.fileName}`,
|
||||
fileName: textFileState.fileName,
|
||||
fileContent: textFileState.fileContent,
|
||||
fileType: textFileState.fileType,
|
||||
observations: textFileState.observations,
|
||||
source: textFileState.source,
|
||||
onComplete: (success, result) => {
|
||||
console.log('Text file minigame completed:', success, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn('No pending text file return state found');
|
||||
}
|
||||
}
|
||||
@@ -302,6 +302,33 @@ export function handleObjectInteraction(sprite) {
|
||||
}
|
||||
}
|
||||
|
||||
// For text_file type objects, use the text file minigame
|
||||
if (data.type === 'text_file' && data.text) {
|
||||
console.log('Text file object detected:', { type: data.type, name: data.name, text: data.text });
|
||||
// Start the text file minigame
|
||||
if (window.MinigameFramework) {
|
||||
// Initialize the framework if not already done
|
||||
if (!window.MinigameFramework.mainGameScene && window.game) {
|
||||
window.MinigameFramework.init(window.game);
|
||||
}
|
||||
|
||||
const minigameParams = {
|
||||
title: `Text File - ${data.name || 'Unknown File'}`,
|
||||
fileName: data.name || 'Unknown File',
|
||||
fileContent: data.text,
|
||||
fileType: data.fileType || 'text',
|
||||
observations: data.observations,
|
||||
source: data.source || 'Unknown Source',
|
||||
onComplete: (success, result) => {
|
||||
console.log('Text file minigame completed:', success, result);
|
||||
}
|
||||
};
|
||||
|
||||
window.MinigameFramework.startMinigame('text-file', null, minigameParams);
|
||||
return; // Exit early since minigame handles the interaction
|
||||
}
|
||||
}
|
||||
|
||||
if (data.readable && data.text) {
|
||||
message += `Text: ${data.text}\n`;
|
||||
|
||||
|
||||
@@ -85,8 +85,10 @@
|
||||
"lockType": "password",
|
||||
"requires": "desktop123",
|
||||
"showHint": false,
|
||||
"showKeyboard": false,
|
||||
"showKeyboard": true,
|
||||
"maxAttempts": 3,
|
||||
"postitNote": "Password: desktop123",
|
||||
"showPostit": true,
|
||||
"locked": true,
|
||||
"contents": [
|
||||
{
|
||||
@@ -112,7 +114,7 @@
|
||||
"observations": "A digital encryption key file."
|
||||
}
|
||||
],
|
||||
"observations": "An office computer with desktop access. Desktop mode will be automatically enabled."
|
||||
"observations": "An office computer with desktop access. Desktop mode will be automatically enabled. Password is on the sticky note!"
|
||||
},
|
||||
{
|
||||
"id": "tablet_device",
|
||||
@@ -127,6 +129,8 @@
|
||||
"showHint": false,
|
||||
"showKeyboard": true,
|
||||
"maxAttempts": 3,
|
||||
"postitNote": "Tablet password: tablet2024",
|
||||
"showPostit": true,
|
||||
"locked": true,
|
||||
"contents": [
|
||||
{
|
||||
@@ -145,7 +149,7 @@
|
||||
"observations": "Sensitive financial information."
|
||||
}
|
||||
],
|
||||
"observations": "An executive tablet device. Desktop mode will be automatically enabled."
|
||||
"observations": "An executive tablet device. Desktop mode will be automatically enabled. Password is written on the sticky note."
|
||||
}
|
||||
],
|
||||
"victoryConditions": [
|
||||
|
||||
@@ -36,23 +36,17 @@
|
||||
"showKeyboard": true,
|
||||
"maxAttempts": 3,
|
||||
"locked": true,
|
||||
"requires": "password",
|
||||
"requires": "secret123",
|
||||
"observations": "The reception's computer, currently locked",
|
||||
"postitNote": "Password: secret123",
|
||||
"showPostit": true,
|
||||
"contents": [
|
||||
{
|
||||
"type": "notes",
|
||||
"name": "Private Note",
|
||||
"takeable": true,
|
||||
"type": "text_file",
|
||||
"name": "Private",
|
||||
"takeable": false,
|
||||
"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"
|
||||
"text": "Closet keypad code: 7391 - Must move evidence to safe before audit"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -144,8 +138,14 @@
|
||||
"type": "pc",
|
||||
"name": "Office Computer",
|
||||
"takeable": false,
|
||||
"requires": "password",
|
||||
"observations": "A standard office computer"
|
||||
"lockType": "password",
|
||||
"requires": "office2024",
|
||||
"showKeyboard": true,
|
||||
"maxAttempts": 3,
|
||||
"locked": true,
|
||||
"postitNote": "Password: office2024",
|
||||
"showPostit": true,
|
||||
"observations": "A standard office computer with a sticky note on the monitor"
|
||||
},
|
||||
{
|
||||
"type": "notes",
|
||||
@@ -212,7 +212,14 @@
|
||||
"type": "pc",
|
||||
"name": "CEO Computer",
|
||||
"takeable": false,
|
||||
"observations": "The CEO's laptop, still warm - recently used"
|
||||
"lockType": "password",
|
||||
"requires": "ceo2024",
|
||||
"showKeyboard": true,
|
||||
"maxAttempts": 3,
|
||||
"locked": true,
|
||||
"postitNote": "Password: ceo2024",
|
||||
"showPostit": true,
|
||||
"observations": "The CEO's laptop, still warm - recently used. A sticky note is attached to the screen."
|
||||
},
|
||||
{
|
||||
"type": "suitcase",
|
||||
|
||||
269
test-container-interactive-items.html
Normal file
269
test-container-interactive-items.html
Normal file
@@ -0,0 +1,269 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Container Interactive Items Test</title>
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/minigames.css">
|
||||
<link rel="stylesheet" href="css/text-file-minigame.css">
|
||||
<style>
|
||||
body {
|
||||
background: #000;
|
||||
color: #00ff00;
|
||||
font-family: 'Courier New', monospace;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.test-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.test-button {
|
||||
background: rgba(0, 255, 0, 0.1);
|
||||
border: 2px solid #00ff00;
|
||||
color: #00ff00;
|
||||
padding: 15px 30px;
|
||||
margin: 10px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.test-button:hover {
|
||||
background: rgba(0, 255, 0, 0.2);
|
||||
box-shadow: 0 0 15px rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
.test-info {
|
||||
background: rgba(0, 255, 0, 0.05);
|
||||
border: 1px solid #00ff00;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.test-info h3 {
|
||||
margin-top: 0;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.test-info p {
|
||||
margin: 5px 0;
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<h1>📦 Container Interactive Items Test</h1>
|
||||
|
||||
<div class="test-info">
|
||||
<h3>Test Instructions:</h3>
|
||||
<p>1. Click the test buttons below to launch different container scenarios</p>
|
||||
<p>2. Test clicking on interactive items within containers</p>
|
||||
<p>3. Verify that interactive items trigger their respective minigames</p>
|
||||
<p>4. Test that takeable items still go to inventory</p>
|
||||
</div>
|
||||
|
||||
<button class="test-button" onclick="testCEOExfilContainer()">
|
||||
🎯 Test CEO Exfil Container (PC with text_file)
|
||||
</button>
|
||||
|
||||
<button class="test-button" onclick="testMixedContainer()">
|
||||
📦 Test Mixed Container (notes, text_file, key)
|
||||
</button>
|
||||
|
||||
<button class="test-button" onclick="testPhoneContainer()">
|
||||
📱 Test Phone Container (phone with messages)
|
||||
</button>
|
||||
|
||||
<button class="test-button" onclick="testWorkstationContainer()">
|
||||
💻 Test Workstation Container (crypto workstation)
|
||||
</button>
|
||||
|
||||
<div class="test-info">
|
||||
<h3>Expected Behavior:</h3>
|
||||
<p>• Interactive items (notes, text_file, phone, workstation) should trigger their minigames</p>
|
||||
<p>• Takeable items (keys, etc.) should go to inventory</p>
|
||||
<p>• After minigame completion, should return to container</p>
|
||||
<p>• Container should show remaining items after interaction</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Minigame container -->
|
||||
<div id="minigame-container" style="display: none;"></div>
|
||||
|
||||
<script type="module">
|
||||
// Import the minigame framework and container minigame
|
||||
import { MinigameFramework } from './js/minigames/framework/minigame-manager.js';
|
||||
import { ContainerMinigame, startContainerMinigame } from './js/minigames/container/container-minigame.js';
|
||||
import { TextFileMinigame } from './js/minigames/text-file/text-file-minigame.js';
|
||||
import { PhoneMessagesMinigame } from './js/minigames/phone/phone-messages-minigame.js';
|
||||
import { NotesMinigame, startNotesMinigame } from './js/minigames/notes/notes-minigame.js';
|
||||
|
||||
// Initialize the framework
|
||||
window.MinigameFramework = MinigameFramework;
|
||||
MinigameFramework.registerScene('container', ContainerMinigame);
|
||||
MinigameFramework.registerScene('text-file', TextFileMinigame);
|
||||
MinigameFramework.registerScene('phone-messages', PhoneMessagesMinigame);
|
||||
MinigameFramework.registerScene('notes', NotesMinigame);
|
||||
|
||||
// Make functions available globally
|
||||
window.startNotesMinigame = startNotesMinigame;
|
||||
window.startContainerMinigame = startContainerMinigame;
|
||||
|
||||
// Mock crypto workstation function
|
||||
window.openCryptoWorkstation = function() {
|
||||
alert('Crypto Workstation opened! (This would normally open the crypto workstation interface)');
|
||||
};
|
||||
|
||||
// Test data
|
||||
const testContainers = {
|
||||
ceoExfil: {
|
||||
containerItem: {
|
||||
scenarioData: {
|
||||
name: 'Reception Computer',
|
||||
type: 'pc',
|
||||
observations: 'The reception computer, currently unlocked'
|
||||
},
|
||||
name: 'pc'
|
||||
},
|
||||
contents: [
|
||||
{
|
||||
type: 'text_file',
|
||||
name: 'Private',
|
||||
takeable: false,
|
||||
readable: true,
|
||||
text: 'Closet keypad code: 7391 - Must move evidence to safe before audit',
|
||||
observations: 'Private file found on reception computer',
|
||||
source: 'Reception Computer'
|
||||
}
|
||||
],
|
||||
isTakeable: false
|
||||
},
|
||||
|
||||
mixed: {
|
||||
containerItem: {
|
||||
scenarioData: {
|
||||
name: 'Desk Drawer',
|
||||
type: 'drawer',
|
||||
observations: 'A desk drawer containing various items'
|
||||
},
|
||||
name: 'drawer'
|
||||
},
|
||||
contents: [
|
||||
{
|
||||
type: 'notes',
|
||||
name: 'Meeting Notes',
|
||||
takeable: false,
|
||||
readable: true,
|
||||
text: 'Important meeting notes about the security audit. Need to review access logs and update passwords.',
|
||||
observations: 'Handwritten meeting notes'
|
||||
},
|
||||
{
|
||||
type: 'text_file',
|
||||
name: 'Security Report',
|
||||
takeable: false,
|
||||
readable: true,
|
||||
text: 'SECURITY AUDIT REPORT\n\nDate: Today\nStatus: In Progress\n\nFindings:\n- Weak passwords detected\n- Unauthorized access attempts\n- Missing security patches\n\nRecommendations:\n- Implement 2FA\n- Update all passwords\n- Install security updates',
|
||||
observations: 'Digital security audit report',
|
||||
source: 'IT Department'
|
||||
},
|
||||
{
|
||||
type: 'key',
|
||||
name: 'Office Key',
|
||||
takeable: true,
|
||||
observations: 'A key that might open something important'
|
||||
}
|
||||
],
|
||||
isTakeable: false
|
||||
},
|
||||
|
||||
phone: {
|
||||
containerItem: {
|
||||
scenarioData: {
|
||||
name: 'Smartphone',
|
||||
type: 'phone',
|
||||
observations: 'A smartphone found on the desk'
|
||||
},
|
||||
name: 'phone'
|
||||
},
|
||||
contents: [
|
||||
{
|
||||
type: 'phone',
|
||||
name: 'Phone Messages',
|
||||
takeable: false,
|
||||
text: 'Hey, did you get the files? The boss is asking about the audit.',
|
||||
voice: 'This is a test voice message. The security audit is scheduled for tomorrow morning.',
|
||||
sender: 'Colleague',
|
||||
timestamp: '2:30 PM',
|
||||
observations: 'Phone with text and voice messages'
|
||||
}
|
||||
],
|
||||
isTakeable: true
|
||||
},
|
||||
|
||||
workstation: {
|
||||
containerItem: {
|
||||
scenarioData: {
|
||||
name: 'Crypto Workstation',
|
||||
type: 'workstation',
|
||||
observations: 'A specialized cryptographic workstation'
|
||||
},
|
||||
name: 'workstation'
|
||||
},
|
||||
contents: [
|
||||
{
|
||||
type: 'workstation',
|
||||
name: 'Crypto Workstation',
|
||||
takeable: false,
|
||||
observations: 'A specialized cryptographic workstation for data analysis'
|
||||
}
|
||||
],
|
||||
isTakeable: false
|
||||
}
|
||||
};
|
||||
|
||||
// Test functions
|
||||
window.testCEOExfilContainer = function() {
|
||||
startContainerMinigame(
|
||||
testContainers.ceoExfil.containerItem,
|
||||
testContainers.ceoExfil.contents,
|
||||
testContainers.ceoExfil.isTakeable
|
||||
);
|
||||
};
|
||||
|
||||
window.testMixedContainer = function() {
|
||||
startContainerMinigame(
|
||||
testContainers.mixed.containerItem,
|
||||
testContainers.mixed.contents,
|
||||
testContainers.mixed.isTakeable
|
||||
);
|
||||
};
|
||||
|
||||
window.testPhoneContainer = function() {
|
||||
startContainerMinigame(
|
||||
testContainers.phone.containerItem,
|
||||
testContainers.phone.contents,
|
||||
testContainers.phone.isTakeable
|
||||
);
|
||||
};
|
||||
|
||||
window.testWorkstationContainer = function() {
|
||||
startContainerMinigame(
|
||||
testContainers.workstation.containerItem,
|
||||
testContainers.workstation.contents,
|
||||
testContainers.workstation.isTakeable
|
||||
);
|
||||
};
|
||||
|
||||
console.log('Container Interactive Items Test Page Loaded');
|
||||
console.log('Available test functions:', Object.keys(window).filter(key => key.startsWith('test')));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,6 +8,7 @@
|
||||
<!-- Include the game's CSS -->
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/minigames.css">
|
||||
<link rel="stylesheet" href="css/password-minigame.css">
|
||||
<link rel="stylesheet" href="css/notifications.css">
|
||||
|
||||
<style>
|
||||
@@ -128,9 +129,9 @@
|
||||
|
||||
<!-- Password with Onscreen Keyboard -->
|
||||
<div class="test-section">
|
||||
<h2>Password with Onscreen Keyboard</h2>
|
||||
<h2>Password with Onscreen Keyboard (with Shift)</h2>
|
||||
<div class="test-description">
|
||||
Test password entry with onscreen QWERTY keyboard. Password: "keyboard"
|
||||
Test password entry with onscreen QWERTY keyboard including shift key for caps. Password: "Keyboard"
|
||||
</div>
|
||||
<button class="test-button" onclick="testPasswordWithKeyboard()">Test Password with Keyboard</button>
|
||||
</div>
|
||||
@@ -218,10 +219,10 @@
|
||||
};
|
||||
|
||||
window.testPasswordWithKeyboard = function() {
|
||||
updateTestResults("Starting password with keyboard test...");
|
||||
updateTestResults("Starting password with keyboard test (try using shift key for caps)...");
|
||||
MinigameFramework.startMinigame('password', null, {
|
||||
title: 'Password with Keyboard Test',
|
||||
password: 'keyboard',
|
||||
password: 'Keyboard',
|
||||
showHint: false,
|
||||
showKeyboard: true,
|
||||
maxAttempts: 3,
|
||||
|
||||
364
test-text-file-minigame.html
Normal file
364
test-text-file-minigame.html
Normal file
@@ -0,0 +1,364 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Text File Minigame Test</title>
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/minigames.css">
|
||||
<link rel="stylesheet" href="css/text-file-minigame.css">
|
||||
<style>
|
||||
body {
|
||||
background: #000;
|
||||
color: #00ff00;
|
||||
font-family: 'Courier New', monospace;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.test-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.test-button {
|
||||
background: rgba(0, 255, 0, 0.1);
|
||||
border: 2px solid #00ff00;
|
||||
color: #00ff00;
|
||||
padding: 15px 30px;
|
||||
margin: 10px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.test-button:hover {
|
||||
background: rgba(0, 255, 0, 0.2);
|
||||
box-shadow: 0 0 15px rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
.test-info {
|
||||
background: rgba(0, 255, 0, 0.05);
|
||||
border: 1px solid #00ff00;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.test-info h3 {
|
||||
margin-top: 0;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.test-info p {
|
||||
margin: 5px 0;
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<h1>📄 Text File Minigame Test</h1>
|
||||
|
||||
<div class="test-info">
|
||||
<h3>Test Instructions:</h3>
|
||||
<p>1. Click the test buttons below to launch different text file scenarios</p>
|
||||
<p>2. Test the "Add to Notebook" functionality</p>
|
||||
<p>3. Test copy and select all features</p>
|
||||
<p>4. Test keyboard shortcuts (Ctrl+A, Ctrl+C, Escape)</p>
|
||||
</div>
|
||||
|
||||
<button class="test-button" onclick="testBasicTextFile()">
|
||||
📄 Test Basic Text File
|
||||
</button>
|
||||
|
||||
<button class="test-button" onclick="testLongTextFile()">
|
||||
📄 Test Long Text File
|
||||
</button>
|
||||
|
||||
<button class="test-button" onclick="testCodeFile()">
|
||||
💻 Test Code File
|
||||
</button>
|
||||
|
||||
<button class="test-button" onclick="testEmptyFile()">
|
||||
📄 Test Empty File
|
||||
</button>
|
||||
|
||||
<button class="test-button" onclick="testCEOExfilFile()">
|
||||
🎯 Test CEO Exfil File (from scenario)
|
||||
</button>
|
||||
|
||||
<div class="test-info">
|
||||
<h3>Expected Behavior:</h3>
|
||||
<p>• Text file minigame should open with Mac-style window decorations</p>
|
||||
<p>• VT323 font should be used for all text content</p>
|
||||
<p>• Black text on white background (clean, readable interface)</p>
|
||||
<p>• Window controls (close, minimize, maximize) should be functional</p>
|
||||
<p>• "Add to Notebook" button should work (if notes minigame is available)</p>
|
||||
<p>• Copy and Select All buttons should function</p>
|
||||
<p>• Escape key should close the minigame</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Minigame container -->
|
||||
<div id="minigame-container" style="display: none;"></div>
|
||||
|
||||
<script type="module">
|
||||
// Import the minigame framework and text file minigame
|
||||
import { MinigameFramework } from './js/minigames/framework/minigame-manager.js';
|
||||
import { TextFileMinigame } from './js/minigames/text-file/text-file-minigame.js';
|
||||
|
||||
// Initialize the framework
|
||||
window.MinigameFramework = MinigameFramework;
|
||||
MinigameFramework.registerScene('text-file', TextFileMinigame);
|
||||
|
||||
// Test data
|
||||
const testFiles = {
|
||||
basic: {
|
||||
fileName: 'readme.txt',
|
||||
fileContent: `Welcome to BreakEscape!
|
||||
|
||||
This is a test text file to demonstrate the text file minigame functionality.
|
||||
|
||||
Features:
|
||||
- File content display
|
||||
- Copy to clipboard
|
||||
- Select all text
|
||||
- Add to notebook
|
||||
- Keyboard shortcuts
|
||||
|
||||
The minigame should handle this content properly and provide a good user experience.`,
|
||||
fileType: 'text',
|
||||
observations: 'A simple text file for testing purposes',
|
||||
source: 'Test Environment'
|
||||
},
|
||||
|
||||
long: {
|
||||
fileName: 'security_report.txt',
|
||||
fileContent: `SECURITY INCIDENT REPORT
|
||||
================================
|
||||
|
||||
Date: ${new Date().toLocaleDateString()}
|
||||
Time: ${new Date().toLocaleTimeString()}
|
||||
Reporter: Security Team
|
||||
Classification: CONFIDENTIAL
|
||||
|
||||
INCIDENT SUMMARY:
|
||||
A potential security breach was detected in the main server room. Unauthorized access was attempted through the ventilation system. The intruder appears to have been familiar with the building layout and security protocols.
|
||||
|
||||
DETAILS:
|
||||
- Time of incident: 02:30 AM
|
||||
- Location: Server Room A, Floor 3
|
||||
- Method: Physical intrusion via ventilation shaft
|
||||
- Duration: Approximately 15 minutes
|
||||
- Damage: None detected
|
||||
- Evidence: Fingerprints on server rack, disturbed dust patterns
|
||||
|
||||
SUSPECT PROFILE:
|
||||
- Height: Approximately 5'8" to 6'0"
|
||||
- Build: Average to athletic
|
||||
- Clothing: Dark clothing, possibly tactical gear
|
||||
- Equipment: Professional lockpicking tools, small flashlight
|
||||
- Knowledge: Familiar with building layout and security systems
|
||||
|
||||
EVIDENCE COLLECTED:
|
||||
1. Fingerprint samples from server rack
|
||||
2. Dust disturbance patterns
|
||||
3. Security camera footage (partially obscured)
|
||||
4. Tool marks on ventilation cover
|
||||
|
||||
RECOMMENDATIONS:
|
||||
1. Increase security patrols in server room area
|
||||
2. Install additional cameras in ventilation access points
|
||||
3. Review and update access control procedures
|
||||
4. Conduct security awareness training for all staff
|
||||
5. Consider upgrading physical security measures
|
||||
|
||||
FOLLOW-UP ACTIONS:
|
||||
- [ ] Review security camera footage
|
||||
- [ ] Interview security personnel on duty
|
||||
- [ ] Check access logs for any anomalies
|
||||
- [ ] Coordinate with law enforcement if necessary
|
||||
- [ ] Update incident response procedures
|
||||
|
||||
This incident highlights the need for enhanced physical security measures and regular security assessments. All personnel should remain vigilant and report any suspicious activity immediately.
|
||||
|
||||
END OF REPORT`,
|
||||
fileType: 'text',
|
||||
observations: 'Confidential security incident report',
|
||||
source: 'Security Department'
|
||||
},
|
||||
|
||||
code: {
|
||||
fileName: 'access_control.py',
|
||||
fileContent: `#!/usr/bin/env python3
|
||||
"""
|
||||
Access Control System
|
||||
Handles authentication and authorization for the BreakEscape system
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
class AccessControl:
|
||||
def __init__(self):
|
||||
self.users = {}
|
||||
self.sessions = {}
|
||||
self.failed_attempts = {}
|
||||
self.max_attempts = 3
|
||||
self.lockout_duration = 300 # 5 minutes
|
||||
|
||||
def hash_password(self, password):
|
||||
"""Hash password using SHA-256"""
|
||||
return hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
def authenticate(self, username, password):
|
||||
"""Authenticate user with username and password"""
|
||||
if username in self.failed_attempts:
|
||||
if self.failed_attempts[username]['count'] >= self.max_attempts:
|
||||
if time.time() - self.failed_attempts[username]['last_attempt'] < self.lockout_duration:
|
||||
return False, "Account locked due to too many failed attempts"
|
||||
|
||||
if username in self.users:
|
||||
hashed_password = self.hash_password(password)
|
||||
if self.users[username]['password'] == hashed_password:
|
||||
# Reset failed attempts on successful login
|
||||
if username in self.failed_attempts:
|
||||
del self.failed_attempts[username]
|
||||
|
||||
# Create session
|
||||
session_id = self.create_session(username)
|
||||
return True, session_id
|
||||
else:
|
||||
self.record_failed_attempt(username)
|
||||
return False, "Invalid password"
|
||||
else:
|
||||
return False, "User not found"
|
||||
|
||||
def create_session(self, username):
|
||||
"""Create a new session for the user"""
|
||||
session_id = hashlib.md5(f"{username}{time.time()}".encode()).hexdigest()
|
||||
self.sessions[session_id] = {
|
||||
'username': username,
|
||||
'created': time.time(),
|
||||
'last_activity': time.time()
|
||||
}
|
||||
return session_id
|
||||
|
||||
def validate_session(self, session_id):
|
||||
"""Validate if session is still active"""
|
||||
if session_id in self.sessions:
|
||||
session = self.sessions[session_id]
|
||||
if time.time() - session['last_activity'] < 3600: # 1 hour timeout
|
||||
session['last_activity'] = time.time()
|
||||
return True, session['username']
|
||||
else:
|
||||
del self.sessions[session_id]
|
||||
return False, "Session expired"
|
||||
return False, "Invalid session"
|
||||
|
||||
def record_failed_attempt(self, username):
|
||||
"""Record a failed login attempt"""
|
||||
if username not in self.failed_attempts:
|
||||
self.failed_attempts[username] = {'count': 0, 'last_attempt': 0}
|
||||
|
||||
self.failed_attempts[username]['count'] += 1
|
||||
self.failed_attempts[username]['last_attempt'] = time.time()
|
||||
|
||||
def add_user(self, username, password, role='user'):
|
||||
"""Add a new user to the system"""
|
||||
if username in self.users:
|
||||
return False, "User already exists"
|
||||
|
||||
self.users[username] = {
|
||||
'password': self.hash_password(password),
|
||||
'role': role,
|
||||
'created': time.time()
|
||||
}
|
||||
return True, "User created successfully"
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
ac = AccessControl()
|
||||
|
||||
# Add some test users
|
||||
ac.add_user("admin", "admin123", "admin")
|
||||
ac.add_user("user1", "password123", "user")
|
||||
|
||||
# Test authentication
|
||||
success, result = ac.authenticate("admin", "admin123")
|
||||
if success:
|
||||
print(f"Login successful! Session ID: {result}")
|
||||
else:
|
||||
print(f"Login failed: {result}")`,
|
||||
fileType: 'python',
|
||||
observations: 'Python code for access control system',
|
||||
source: 'Development Team'
|
||||
},
|
||||
|
||||
empty: {
|
||||
fileName: 'empty.txt',
|
||||
fileContent: '',
|
||||
fileType: 'text',
|
||||
observations: 'An empty text file',
|
||||
source: 'Test Environment'
|
||||
},
|
||||
|
||||
ceoExfil: {
|
||||
fileName: 'Private',
|
||||
fileContent: 'Closet keypad code: 7391 - Must move evidence to safe before audit',
|
||||
fileType: 'text',
|
||||
observations: 'Private file found on reception computer',
|
||||
source: 'Reception Computer'
|
||||
}
|
||||
};
|
||||
|
||||
// Test functions
|
||||
window.testBasicTextFile = function() {
|
||||
startTextFileMinigame(testFiles.basic);
|
||||
};
|
||||
|
||||
window.testLongTextFile = function() {
|
||||
startTextFileMinigame(testFiles.long);
|
||||
};
|
||||
|
||||
window.testCodeFile = function() {
|
||||
startTextFileMinigame(testFiles.code);
|
||||
};
|
||||
|
||||
window.testEmptyFile = function() {
|
||||
startTextFileMinigame(testFiles.empty);
|
||||
};
|
||||
|
||||
window.testCEOExfilFile = function() {
|
||||
startTextFileMinigame(testFiles.ceoExfil);
|
||||
};
|
||||
|
||||
function startTextFileMinigame(fileData) {
|
||||
const container = document.getElementById('minigame-container');
|
||||
container.style.display = 'block';
|
||||
|
||||
const minigameParams = {
|
||||
title: `Text File - ${fileData.fileName}`,
|
||||
fileName: fileData.fileName,
|
||||
fileContent: fileData.fileContent,
|
||||
fileType: fileData.fileType,
|
||||
observations: fileData.observations,
|
||||
source: fileData.source,
|
||||
onComplete: (success, result) => {
|
||||
console.log('Text file minigame completed:', success, result);
|
||||
container.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
MinigameFramework.startMinigame('text-file', container, minigameParams);
|
||||
}
|
||||
|
||||
console.log('Text File Minigame Test Page Loaded');
|
||||
console.log('Available test functions:', Object.keys(window).filter(key => key.startsWith('test')));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,6 +8,7 @@
|
||||
<!-- Include the game's CSS -->
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/minigames.css">
|
||||
<link rel="stylesheet" href="css/password-minigame.css">
|
||||
<link rel="stylesheet" href="css/notifications.css">
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user