5477 lines
247 KiB
HTML
5477 lines
247 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Gooner Training Academy</title>
|
||
|
||
<!-- Core Styles -->
|
||
<link rel="stylesheet" href="src/styles/color-variables.css">
|
||
<link rel="stylesheet" href="src/styles/styles.css">
|
||
<link rel="stylesheet" href="src/styles/styles-dark-edgy.css">
|
||
<link rel="stylesheet" href="src/styles/base-video-player.css">
|
||
<script src="src/utils/themeManager.js"></script>
|
||
|
||
<style>
|
||
/* Training Academy Specific Styles */
|
||
/* Academy Header - Matching Quick Play/Cinema Style */
|
||
.academy-header {
|
||
background: var(--gradient-primary);
|
||
border-bottom: 2px solid var(--header-border);
|
||
box-shadow: var(--shadow-md);
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
width: 100%;
|
||
z-index: 1000;
|
||
backdrop-filter: blur(10px);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.academy-nav {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 1rem 2rem;
|
||
gap: 2rem;
|
||
width: 100%;
|
||
}
|
||
|
||
.academy-nav .nav-left h1 {
|
||
color: var(--header-title-color);
|
||
font-size: 1.8rem;
|
||
margin: 0;
|
||
text-shadow: var(--shadow-glow-primary);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.academy-nav .nav-center {
|
||
flex: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.academy-subtitle {
|
||
color: var(--header-subtitle-color);
|
||
font-size: 1rem;
|
||
font-style: italic;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.academy-nav .nav-right {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
align-items: center;
|
||
}
|
||
|
||
/* Button Styles */
|
||
.btn {
|
||
background: var(--btn-secondary-bg);
|
||
border: 1px solid var(--btn-secondary-border);
|
||
color: var(--btn-secondary-text);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 0.9rem;
|
||
transition: all 0.3s ease;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.btn:hover {
|
||
background: var(--btn-secondary-hover-bg);
|
||
border-color: var(--btn-secondary-hover-border);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: var(--bg-secondary-overlay-20);
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: var(--bg-secondary-overlay-30);
|
||
border-color: var(--color-primary-light);
|
||
}
|
||
|
||
.btn-warning {
|
||
background: var(--btn-danger-bg);
|
||
border-color: var(--btn-danger-border);
|
||
}
|
||
|
||
.btn-warning:hover {
|
||
background: var(--btn-danger-hover-bg);
|
||
border-color: var(--btn-danger-hover-border);
|
||
}
|
||
|
||
.academy-controls .btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.6rem 1rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.academy-controls .btn-icon {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
/* Responsive adjustments */
|
||
@media (max-width: 768px) {
|
||
.academy-nav {
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
padding: 1rem;
|
||
}
|
||
|
||
.academy-subtitle {
|
||
display: none;
|
||
}
|
||
|
||
.academy-controls {
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
|
||
.academy-controls .btn-text {
|
||
display: none;
|
||
}
|
||
|
||
.academy-controls .btn {
|
||
padding: 0.6rem;
|
||
}
|
||
}
|
||
|
||
/* Scenario Status Bar */
|
||
.scenario-status-bar {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
background: var(--bg-secondary-overlay-10);
|
||
border: 1px solid var(--border-secondary);
|
||
border-radius: 10px;
|
||
padding: 1rem;
|
||
backdrop-filter: blur(5px);
|
||
position: fixed;
|
||
top: 100px;
|
||
left: 20px;
|
||
z-index: 10000;
|
||
min-width: 200px;
|
||
}
|
||
|
||
.scenario-status-bar .status-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
text-align: left;
|
||
padding: 0.5rem;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.scenario-status-bar .status-label {
|
||
color: var(--color-secondary);
|
||
font-size: 0.85rem;
|
||
font-weight: bold;
|
||
margin-bottom: 0.2rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.scenario-status-bar .status-value {
|
||
color: #ffffff;
|
||
font-size: 1.1rem;
|
||
font-weight: bold;
|
||
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.scenario-status-bar .timer-display {
|
||
font-family: 'Courier New', monospace;
|
||
color: var(--color-accent-green);
|
||
text-shadow: 0 0 10px var(--bg-primary-overlay-20);
|
||
}
|
||
|
||
.training-controls {
|
||
background: var(--bg-secondary-overlay-10);
|
||
border: 1px solid var(--border-secondary);
|
||
border-radius: 10px;
|
||
padding: 1.5rem;
|
||
margin: 1rem;
|
||
}
|
||
|
||
.library-status {
|
||
background: var(--bg-primary-overlay-10);
|
||
border: 1px solid var(--color-accent-blue);
|
||
border-radius: 8px;
|
||
padding: 1rem;
|
||
margin: 1rem 0;
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
.library-status h3 {
|
||
color: var(--color-accent-blue);
|
||
margin: 0 0 0.5rem 0;
|
||
}
|
||
|
||
.academy-mode-selection {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
margin: 1rem auto;
|
||
max-width: 1000px;
|
||
}
|
||
|
||
.training-mode-card {
|
||
background: var(--gradient-dark);
|
||
border: 2px solid var(--color-primary);
|
||
border-radius: 8px;
|
||
padding: 0.8rem;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
text-align: center;
|
||
margin: 0.5rem;
|
||
width: 200px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.training-mode-card:hover {
|
||
border-color: var(--card-hover-border);
|
||
box-shadow: var(--shadow-glow-primary);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.training-mode-card.selected {
|
||
border-color: var(--card-hover-border);
|
||
background: var(--gradient-accent);
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.mode-icon {
|
||
font-size: 1.8rem;
|
||
margin-bottom: 0.4rem;
|
||
}
|
||
|
||
.mode-name {
|
||
font-size: 1rem;
|
||
font-weight: bold;
|
||
color: var(--text-primary);
|
||
margin-bottom: 0.2rem;
|
||
}
|
||
|
||
.mode-description {
|
||
color: var(--text-muted);
|
||
font-size: 0.75rem;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.academy-start-controls {
|
||
text-align: center;
|
||
margin: 1rem;
|
||
}
|
||
|
||
.start-training-btn {
|
||
background: var(--gradient-accent);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 25px;
|
||
padding: 1rem 2rem;
|
||
font-size: 1.2rem;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.start-training-btn:hover:not(:disabled) {
|
||
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-secondary-dark));
|
||
box-shadow: 0 6px 20px var(--bg-primary-overlay-20);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.start-training-btn:disabled {
|
||
background: var(--text-dim);
|
||
cursor: not-allowed;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.back-navigation {
|
||
position: fixed;
|
||
top: 20px;
|
||
left: 20px;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.back-btn {
|
||
background: var(--bg-secondary-overlay-90);
|
||
color: var(--text-primary);
|
||
border: 1px solid var(--border-primary);
|
||
border-radius: 8px;
|
||
padding: 0.5rem 1rem;
|
||
text-decoration: none;
|
||
font-weight: bold;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.back-btn:hover {
|
||
background: var(--bg-secondary);
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
/* Mirror Task Action Buttons */
|
||
.action-btn {
|
||
background: linear-gradient(135deg, #27ae60, #2ecc71);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 0.75rem 1.5rem;
|
||
font-size: 1rem;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
|
||
margin: 0.5rem;
|
||
}
|
||
|
||
.action-btn:hover {
|
||
background: linear-gradient(135deg, #219a52, #27ae60);
|
||
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.skip-btn {
|
||
background: linear-gradient(135deg, #95a5a6, #bdc3c7);
|
||
color: var(--text-dark);
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 0.75rem 1.5rem;
|
||
font-size: 1rem;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 4px 15px rgba(149, 165, 166, 0.3);
|
||
margin: 0.5rem;
|
||
}
|
||
|
||
.skip-btn:hover {
|
||
background: linear-gradient(135deg, #7f8c8d, #95a5a6);
|
||
box-shadow: 0 6px 20px rgba(149, 165, 166, 0.4);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.next-btn {
|
||
background: linear-gradient(135deg, #3498db, #5dade2);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 0.75rem 1.5rem;
|
||
font-size: 1rem;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
|
||
margin: 0.5rem;
|
||
}
|
||
|
||
.next-btn:hover {
|
||
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
|
||
box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
/* Video player container for background videos */
|
||
.training-video-container {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: -1;
|
||
background: var(--bg-black);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.training-video-container video {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
opacity: 0.3;
|
||
}
|
||
|
||
/* Ensure main content is above video */
|
||
.main-content {
|
||
position: relative;
|
||
z-index: 1;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
min-height: 100vh;
|
||
padding-top: 80px; /* Space for fixed header */
|
||
}
|
||
|
||
/* Video controls overlay */
|
||
.video-controls-overlay {
|
||
position: fixed;
|
||
top: 200px;
|
||
right: 20px;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
border-radius: 10px;
|
||
padding: 10px;
|
||
z-index: 100;
|
||
display: none; /* Hidden until video loads */
|
||
}
|
||
|
||
.video-controls-overlay button {
|
||
background: #3498db;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 5px;
|
||
padding: 5px 10px;
|
||
margin: 2px;
|
||
cursor: pointer;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.video-controls-overlay button:hover {
|
||
background: #2980b9;
|
||
}
|
||
|
||
/* Training Academy Specific Styles */
|
||
.training-status, .training-task {
|
||
background: var(--card-bg);
|
||
border: 2px solid var(--card-border);
|
||
border-radius: 15px;
|
||
padding: 25px;
|
||
margin: 20px;
|
||
text-align: center;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.training-task {
|
||
text-align: left;
|
||
max-width: 800px;
|
||
margin: 20px auto;
|
||
}
|
||
|
||
.training-task h3 {
|
||
color: var(--color-primary);
|
||
margin-bottom: 15px;
|
||
text-align: center;
|
||
}
|
||
|
||
.task-content {
|
||
background: var(--bg-overlay-light);
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
margin: 20px 0;
|
||
border-left: 4px solid var(--color-secondary);
|
||
font-size: 1.1em;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.task-story {
|
||
background: rgba(58, 134, 255, 0.2);
|
||
padding: 15px;
|
||
border-radius: 10px;
|
||
margin: 15px 0;
|
||
border-left: 4px solid var(--color-accent);
|
||
font-size: 1em;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.task-story p {
|
||
margin: 0;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.training-controls {
|
||
display: flex;
|
||
gap: 15px;
|
||
justify-content: center;
|
||
margin-top: 25px;
|
||
}
|
||
|
||
.complete-btn, .next-btn {
|
||
background: var(--gradient-accent);
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 25px;
|
||
border-radius: 25px;
|
||
cursor: pointer;
|
||
font-weight: bold;
|
||
font-size: 1em;
|
||
transition: all 0.3s ease;
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.complete-btn:hover, .next-btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.next-btn {
|
||
background: linear-gradient(45deg, #8338ec, #3a86ff);
|
||
}
|
||
|
||
.next-btn:hover {
|
||
box-shadow: 0 8px 20px rgba(131, 56, 236, 0.4);
|
||
}
|
||
|
||
.interactive-notice {
|
||
background: linear-gradient(45deg, rgba(131, 56, 236, 0.2), rgba(58, 134, 255, 0.2));
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 10px;
|
||
padding: 15px;
|
||
margin: 15px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.interactive-notice p {
|
||
margin: 0;
|
||
color: var(--color-secondary);
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* Scenario Adventure Styles */
|
||
.scenario-choice, .scenario-action, .scenario-ending, .scenario-action-progress {
|
||
background: rgba(20, 20, 20, 0.95);
|
||
border: 2px solid var(--color-danger);
|
||
border-radius: 15px;
|
||
padding: 25px;
|
||
margin: 20px auto;
|
||
max-width: 900px;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
/* Training task display container */
|
||
#training-task-display {
|
||
scrollbar-width: thin;
|
||
scrollbar-color: var(--color-primary) rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
#training-task-display::-webkit-scrollbar {
|
||
width: 8px;
|
||
}
|
||
|
||
#training-task-display::-webkit-scrollbar-track {
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
#training-task-display::-webkit-scrollbar-thumb {
|
||
background: var(--color-primary);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
#training-task-display::-webkit-scrollbar-thumb:hover {
|
||
background: var(--color-primary-light);
|
||
}
|
||
|
||
.scenario-story {
|
||
background: rgba(0, 0, 0, 0.8);
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
margin: 20px 0;
|
||
border-left: 4px solid var(--color-danger);
|
||
font-size: 1.1em;
|
||
line-height: 1.6;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.scenario-choices {
|
||
display: grid;
|
||
gap: 15px;
|
||
margin: 25px 0;
|
||
}
|
||
|
||
.choice-option {
|
||
background: linear-gradient(135deg, rgba(131, 56, 236, 0.4), rgba(58, 134, 255, 0.3));
|
||
border: 2px solid var(--color-secondary);
|
||
border-radius: 12px;
|
||
padding: 25px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
margin: 10px 0;
|
||
}
|
||
|
||
.choice-option:hover {
|
||
background: linear-gradient(135deg, rgba(131, 56, 236, 0.7), rgba(58, 134, 255, 0.5));
|
||
border-color: var(--color-accent);
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 8px 25px rgba(131, 56, 236, 0.4);
|
||
}
|
||
|
||
.choice-option h4 {
|
||
color: var(--color-danger);
|
||
margin: 0 0 12px 0;
|
||
font-size: 1.3em;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.choice-preview {
|
||
color: var(--text-muted);
|
||
margin: 0;
|
||
font-style: italic;
|
||
font-size: 1em;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.action-details {
|
||
background: rgba(58, 134, 255, 0.2);
|
||
border: 1px solid var(--color-accent);
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.action-text {
|
||
color: var(--color-accent);
|
||
font-weight: bold;
|
||
font-size: 1.1em;
|
||
margin: 10px 0;
|
||
}
|
||
|
||
.duration-text {
|
||
color: var(--color-warning);
|
||
font-weight: bold;
|
||
margin: 10px 0;
|
||
}
|
||
|
||
.action-timer {
|
||
background: rgba(0, 0, 0, 0.7);
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.progress-bar {
|
||
width: 100%;
|
||
height: 20px;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
margin: 15px 0;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #ff006e, #8338ec);
|
||
width: 0%;
|
||
transition: width 1s ease;
|
||
}
|
||
|
||
.ending-details {
|
||
background: linear-gradient(135deg, rgba(255, 215, 0, 0.2), rgba(255, 140, 0, 0.2));
|
||
border: 2px solid var(--color-gold);
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.ending-details h4 {
|
||
color: var(--color-gold);
|
||
margin: 0 0 15px 0;
|
||
font-size: 1.3em;
|
||
}
|
||
|
||
/* Photo Session Styles */
|
||
.photo-section {
|
||
background: linear-gradient(135deg, rgba(138, 43, 226, 0.2), rgba(255, 20, 147, 0.2));
|
||
border: 2px solid var(--color-purple);
|
||
border-radius: 15px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.photo-capture-controls {
|
||
background: rgba(0, 0, 0, 0.5);
|
||
border-radius: 10px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.photo-capture-controls button {
|
||
background: linear-gradient(135deg, #ff1493, #8a2be2);
|
||
border: none;
|
||
color: white;
|
||
padding: 12px 24px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 1em;
|
||
font-weight: bold;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.photo-capture-controls button:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(255, 20, 147, 0.4);
|
||
}
|
||
|
||
#photo-progress {
|
||
color: var(--color-pink);
|
||
font-weight: bold;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
#webcam-container {
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
max-width: 100%;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.outcome-text {
|
||
color: var(--color-orange);
|
||
font-weight: bold;
|
||
margin: 0;
|
||
}
|
||
|
||
/* Focus Task Styles */
|
||
.focus-task-simple, .focus-session-active, .focus-completed {
|
||
background: rgba(20, 20, 20, 0.95);
|
||
border: 2px solid var(--color-purple);
|
||
border-radius: 15px;
|
||
padding: 25px;
|
||
margin: 20px auto;
|
||
max-width: 800px;
|
||
text-align: center;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.focus-timer-large {
|
||
font-size: 72px;
|
||
font-weight: bold;
|
||
color: var(--color-purple);
|
||
margin: 30px 0;
|
||
text-shadow: 0 0 20px rgba(103, 58, 183, 0.5);
|
||
}
|
||
|
||
.focus-instructions {
|
||
background: rgba(103, 58, 183, 0.2);
|
||
border: 1px solid var(--color-purple);
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.focus-instructions p {
|
||
margin: 8px 0;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.focus-status {
|
||
margin: 20px 0;
|
||
}
|
||
|
||
#focus-status-text {
|
||
font-size: 1.1em;
|
||
font-weight: bold;
|
||
color: var(--color-purple);
|
||
transition: color 0.3s ease;
|
||
}
|
||
|
||
.completion-message {
|
||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.3), rgba(139, 195, 74, 0.2));
|
||
border: 2px solid var(--color-success);
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.completion-message p {
|
||
margin: 10px 0;
|
||
color: var(--color-success-light);
|
||
}
|
||
|
||
/* Video Control Panel Styles */
|
||
.control-sidebar {
|
||
position: fixed;
|
||
top: 100px;
|
||
right: 20px;
|
||
width: 220px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 10px;
|
||
padding: 15px;
|
||
gap: 10px;
|
||
height: fit-content;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.video-control-panel {
|
||
background: rgba(0, 0, 0, 0.9);
|
||
border: 1px solid rgba(139, 92, 246, 0.4);
|
||
border-radius: 8px;
|
||
margin-bottom: 15px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.video-control-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 10px 12px;
|
||
background: rgba(139, 92, 246, 0.2);
|
||
cursor: pointer;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.video-control-header:hover {
|
||
background: rgba(139, 92, 246, 0.3);
|
||
}
|
||
|
||
.video-control-icon {
|
||
font-size: 1.2rem;
|
||
}
|
||
|
||
.video-control-title {
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
flex: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.video-control-toggle {
|
||
font-size: 0.9rem;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.video-control-toggle.collapsed {
|
||
transform: rotate(-90deg);
|
||
}
|
||
|
||
.video-control-content {
|
||
padding: 12px;
|
||
display: block;
|
||
transition: all 0.3s ease;
|
||
max-height: 400px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.video-control-content.collapsed {
|
||
display: none;
|
||
max-height: 0;
|
||
padding: 0 12px;
|
||
}
|
||
|
||
.control-group {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.control-group:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.control-label {
|
||
display: block;
|
||
font-size: 0.8rem;
|
||
color: var(--text-muted);
|
||
margin-bottom: 5px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.control-row {
|
||
display: flex;
|
||
gap: 6px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.control-btn {
|
||
background: rgba(139, 92, 246, 0.2);
|
||
color: var(--text-primary);
|
||
border: 1px solid rgba(139, 92, 246, 0.4);
|
||
border-radius: 6px;
|
||
padding: 6px 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
font-size: 0.8rem;
|
||
flex: 1;
|
||
min-width: 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.control-btn:hover {
|
||
background: rgba(139, 92, 246, 0.4);
|
||
border-color: rgba(139, 92, 246, 0.6);
|
||
}
|
||
|
||
.volume-control {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.volume-slider {
|
||
flex: 1;
|
||
height: 4px;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 2px;
|
||
outline: none;
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
}
|
||
|
||
.volume-slider::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 14px;
|
||
height: 14px;
|
||
background: var(--color-secondary);
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.volume-display {
|
||
font-size: 0.8rem;
|
||
color: var(--text-muted);
|
||
min-width: 35px;
|
||
text-align: right;
|
||
}
|
||
|
||
.playlist-select {
|
||
width: 100%;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
color: var(--text-primary);
|
||
border: 1px solid rgba(139, 92, 246, 0.4);
|
||
border-radius: 6px;
|
||
padding: 6px 8px;
|
||
font-size: 0.8rem;
|
||
outline: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.playlist-select:hover {
|
||
border-color: rgba(139, 92, 246, 0.6);
|
||
}
|
||
|
||
.playlist-select:focus {
|
||
border-color: var(--color-secondary);
|
||
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.2);
|
||
}
|
||
|
||
.video-info {
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border-radius: 6px;
|
||
padding: 8px;
|
||
}
|
||
|
||
.current-video-name {
|
||
font-size: 0.8rem;
|
||
color: var(--text-primary);
|
||
margin-bottom: 6px;
|
||
text-align: center;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.video-progress {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.progress-bar {
|
||
height: 4px;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #8B5CF6, #EC4899);
|
||
width: 0%;
|
||
transition: width 0.1s ease;
|
||
}
|
||
|
||
.video-time {
|
||
font-size: 0.7rem;
|
||
color: var(--text-muted);
|
||
text-align: center;
|
||
}
|
||
|
||
/* Academy Setup Screen Styles */
|
||
.academy-setup {
|
||
padding: 20px;
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.setup-container {
|
||
background: var(--bg-overlay-medium);
|
||
border: 2px solid var(--color-primary);
|
||
border-radius: 15px;
|
||
padding: 30px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.setup-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.setup-header h2 {
|
||
color: var(--color-primary);
|
||
font-family: 'Audiowide', sans-serif;
|
||
font-size: 28px;
|
||
margin: 0 0 10px 0;
|
||
text-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.setup-header p {
|
||
color: var(--text-muted);
|
||
font-size: 16px;
|
||
margin: 0;
|
||
}
|
||
|
||
.setup-instructions {
|
||
margin-top: 25px;
|
||
}
|
||
|
||
.instruction-box {
|
||
background: linear-gradient(135deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.4)), var(--bg-primary-overlay-20);
|
||
border: 1px solid var(--border-primary);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
text-align: left;
|
||
}
|
||
|
||
.setup-content {
|
||
margin-top: 30px;
|
||
}
|
||
|
||
.setting-group {
|
||
background: var(--bg-primary-overlay-10);
|
||
border: 1px solid var(--border-primary);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.setting-group h3 {
|
||
color: var(--color-primary);
|
||
font-family: 'Electrolize', sans-serif;
|
||
font-size: 18px;
|
||
margin: 0 0 15px 0;
|
||
text-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.setting-description {
|
||
margin-bottom: 15px;
|
||
padding: 12px;
|
||
background: var(--bg-primary-overlay-10);
|
||
border-left: 3px solid var(--color-primary);
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.video-settings,
|
||
.tts-settings,
|
||
.message-settings,
|
||
.popup-settings {
|
||
padding: 10px 0;
|
||
}
|
||
|
||
.video-option {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12px;
|
||
background: var(--bg-primary-overlay-10);
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border-primary);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.video-option:hover {
|
||
background: var(--bg-primary-overlay-20);
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
.video-option input[type="checkbox"] {
|
||
width: 20px;
|
||
height: 20px;
|
||
margin-right: 12px;
|
||
cursor: pointer;
|
||
accent-color: var(--color-primary);
|
||
}
|
||
|
||
.video-option label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
cursor: pointer;
|
||
flex: 1;
|
||
font-family: 'Electrolize', sans-serif;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.option-icon {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.option-text {
|
||
font-size: 15px;
|
||
}
|
||
|
||
.video-options,
|
||
.tts-options {
|
||
margin-top: 15px;
|
||
padding: 15px;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border-primary);
|
||
}
|
||
|
||
.setting-row {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.setting-row:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.setting-row label {
|
||
display: block;
|
||
color: var(--text-primary);
|
||
font-family: 'Electrolize', sans-serif;
|
||
font-size: 14px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.slider-container {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.slider {
|
||
flex: 1;
|
||
height: 6px;
|
||
border-radius: 3px;
|
||
background: var(--bg-primary-overlay-20);
|
||
outline: none;
|
||
-webkit-appearance: none;
|
||
}
|
||
|
||
.slider::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 50%;
|
||
background: var(--color-primary);
|
||
cursor: pointer;
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.slider::-moz-range-thumb {
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 50%;
|
||
background: var(--color-primary);
|
||
cursor: pointer;
|
||
border: none;
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
/* Floating Start Button */
|
||
.floating-start-button {
|
||
position: fixed;
|
||
bottom: 30px;
|
||
right: 30px;
|
||
z-index: 1000;
|
||
padding: 18px 35px;
|
||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
|
||
border: none;
|
||
border-radius: 50px;
|
||
color: var(--text-white);
|
||
font-family: 'Electrolize', sans-serif;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
box-shadow: var(--shadow-glow-primary);
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
animation: pulse-glow 2s ease-in-out infinite;
|
||
}
|
||
|
||
.floating-start-button:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 12px 35px var(--bg-primary-overlay-20);
|
||
background: linear-gradient(135deg, var(--color-primary-light) 0%, var(--color-primary) 100%);
|
||
}
|
||
|
||
.floating-start-button:active {
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.floating-start-button .btn-icon {
|
||
font-size: 24px;
|
||
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
|
||
}
|
||
|
||
.floating-start-button .btn-text {
|
||
font-size: 18px;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.floating-start-button:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
background: var(--text-dim);
|
||
box-shadow: none;
|
||
}
|
||
|
||
@keyframes pulse-glow {
|
||
0%, 100% {
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
50% {
|
||
box-shadow: 0 8px 35px var(--bg-primary-overlay-20);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.floating-start-button {
|
||
bottom: 20px;
|
||
right: 20px;
|
||
padding: 15px 28px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.floating-start-button .btn-icon {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.floating-start-button .btn-text {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Page Header -->
|
||
<header class="academy-header">
|
||
<div class="academy-nav">
|
||
<div class="nav-left">
|
||
<h1>🎓 Training Academy</h1>
|
||
</div>
|
||
<div class="nav-center academy-subtitle">
|
||
Master the Art of Dedicated Gooning
|
||
</div>
|
||
<div class="nav-right academy-controls">
|
||
<!-- Theme Switcher -->
|
||
<div id="theme-switcher-container"></div>
|
||
|
||
<button id="tts-toggle" onclick="toggleTTS()" class="btn btn-secondary" title="Toggle voice narration on/off">
|
||
<span class="btn-icon">🔊</span>
|
||
<span class="btn-text">Voice: ON</span>
|
||
</button>
|
||
<button id="tts-stop" onclick="stopTTS()" class="btn btn-warning" title="Stop current voice narration">
|
||
<span class="btn-icon">⏹️</span>
|
||
<span class="btn-text">Stop</span>
|
||
</button>
|
||
<button id="back-to-home" class="btn btn-secondary" onclick="window.location.href='index.html'" title="Return to main menu">
|
||
<span class="btn-icon">🏠</span>
|
||
<span class="btn-text">Home</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Background Video Container -->
|
||
<div class="training-video-container" id="trainingVideoContainer">
|
||
<!-- Video will be inserted here -->
|
||
</div>
|
||
|
||
<!-- Video Control Panel (Collapsible) -->
|
||
<div class="control-sidebar">
|
||
<div id="video-control-panel" class="video-control-panel">
|
||
<div class="video-control-header" onclick="toggleVideoControls()">
|
||
<span class="video-control-icon">🎬</span>
|
||
<span class="video-control-title">Video Controls</span>
|
||
<span id="video-control-toggle" class="video-control-toggle collapsed">▶</span>
|
||
</div>
|
||
<div id="video-control-content" class="video-control-content collapsed">
|
||
<!-- Playback Controls -->
|
||
<div class="control-group">
|
||
<div class="control-row">
|
||
<button id="video-rewind" class="control-btn" title="Rewind 10s">⏪</button>
|
||
<button id="video-play-pause" class="control-btn" title="Play/Pause">⏸️</button>
|
||
<button id="video-forward" class="control-btn" title="Forward 10s">⏩</button>
|
||
<button id="video-skip" class="control-btn" title="Skip Video">⏭️</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Volume Control -->
|
||
<div class="control-group">
|
||
<label class="control-label">🔊 Volume</label>
|
||
<div class="volume-control">
|
||
<input type="range" id="video-volume" class="volume-slider" min="0" max="100" value="50">
|
||
<span id="video-volume-display" class="volume-display">50%</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Playlist Selection -->
|
||
<div class="control-group">
|
||
<label class="control-label">📂 Source</label>
|
||
<select id="video-playlist-select" class="playlist-select">
|
||
<option value="random">🎲 Random Videos</option>
|
||
<option value="playlist1">📝 Playlist 1</option>
|
||
<option value="playlist2">📝 Playlist 2</option>
|
||
<option value="playlist3">📝 Playlist 3</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- TTS Controls -->
|
||
<div class="control-group">
|
||
<label class="control-label">🔊 Voice Narration</label>
|
||
<div class="control-row">
|
||
<button id="sidebar-tts-toggle" onclick="toggleTTS()" class="control-btn" title="Toggle voice narration on/off">
|
||
🔊 ON
|
||
</button>
|
||
<button id="sidebar-tts-stop" onclick="stopTTS()" class="control-btn" title="Stop current speech">
|
||
⏹️ Stop
|
||
</button>
|
||
</div>
|
||
<div id="sidebar-tts-status" class="control-label" style="margin-top: 5px; font-size: 0.75rem; color: var(--text-muted);">
|
||
Voice ready
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Video Info -->
|
||
<div class="control-group">
|
||
<div class="video-info">
|
||
<div id="current-video-name" class="current-video-name">No video loaded</div>
|
||
<div id="video-progress" class="video-progress">
|
||
<div class="progress-bar">
|
||
<div id="progress-fill" class="progress-fill"></div>
|
||
</div>
|
||
<div id="video-time" class="video-time">00:00 / 00:00</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Content -->
|
||
<div class="main-content">
|
||
|
||
<!-- Game Status Bar -->
|
||
<div class="scenario-status-bar" id="scenario-status-bar" style="display: none;">
|
||
<div class="status-item">
|
||
<span class="status-label">TIME:</span>
|
||
<span id="scenario-timer" class="status-value timer-display">00:00</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span class="status-label">XP:</span>
|
||
<span id="scenario-current-xp" class="status-value">0</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Training Academy Setup Screen -->
|
||
<div class="academy-setup" id="academy-setup">
|
||
<div class="setup-container">
|
||
<div class="setup-title">
|
||
<h2 style="text-align: center; color: var(--color-primary); font-family: 'Audiowide', sans-serif; font-size: 28px; margin: 20px 0; text-shadow: var(--shadow-glow-primary);">🎓 Training Academy Setup</h2>
|
||
<p style="text-align: center; color: var(--text-muted); font-size: 16px; margin-bottom: 30px;">Configure your training experience and select your program</p>
|
||
</div>
|
||
|
||
<!-- Floating Start Button -->
|
||
<button id="floating-academy-start-btn" class="floating-start-button" title="Begin your training session with current settings">
|
||
<span class="btn-icon">🎓</span>
|
||
<span class="btn-text">Start Training</span>
|
||
</button>
|
||
|
||
<div class="setup-content">
|
||
<!-- Training Mode Selection -->
|
||
<div class="setting-group">
|
||
<h3>🎯 Training Program</h3>
|
||
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
|
||
<strong style="color: var(--color-primary); font-size: 15px;">Select the training program that matches your goals. Each mode offers unique tasks and challenges designed to enhance your experience.</strong>
|
||
</div>
|
||
<div class="academy-mode-selection" id="trainingModeSelection">
|
||
<!-- Training modes will be populated here -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Video Settings -->
|
||
<div class="setting-group">
|
||
<h3>🎬 Video Experience</h3>
|
||
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
|
||
<strong style="color: var(--color-primary); font-size: 15px;">Background videos enhance immersion during training. Enable or disable video playback based on your preference and system performance.</strong>
|
||
</div>
|
||
<div class="video-settings">
|
||
<div class="video-option">
|
||
<input type="checkbox" id="academy-enable-video" checked>
|
||
<label for="academy-enable-video">
|
||
<span class="option-icon">🎬</span>
|
||
<span class="option-text">Enable Background Video</span>
|
||
</label>
|
||
</div>
|
||
<div class="video-options" id="academy-video-options" style="display: block;">
|
||
<div class="setting-row">
|
||
<label for="academy-video-opacity">Video Opacity: <span id="academy-video-opacity-value">70%</span></label>
|
||
<div class="slider-container">
|
||
<input type="range" id="academy-video-opacity" min="0.1" max="1.0" step="0.05" value="0.7" class="slider">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TTS Settings -->
|
||
<div class="setting-group">
|
||
<h3>🔊 Voice Narration (TTS)</h3>
|
||
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
|
||
<strong style="color: var(--color-primary); font-size: 15px;">Text-to-speech narration provides verbal instructions and encouragement throughout your session. Customize voice settings and volume.</strong>
|
||
</div>
|
||
<div class="tts-settings">
|
||
<div class="video-option">
|
||
<input type="checkbox" id="academy-enable-tts" checked>
|
||
<label for="academy-enable-tts">
|
||
<span class="option-icon">🔊</span>
|
||
<span class="option-text">Enable TTS Narration</span>
|
||
</label>
|
||
</div>
|
||
<div class="tts-options" id="academy-tts-options" style="display: block;">
|
||
<div class="setting-row">
|
||
<label for="academy-tts-volume">TTS Volume: <span id="academy-tts-volume-value">80%</span></label>
|
||
<div class="slider-container">
|
||
<input type="range" id="academy-tts-volume" min="0" max="100" value="80" class="slider">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Game Interface (Hidden initially) -->
|
||
<div id="gameInterface" style="display: none;">
|
||
<!-- Full game interface container -->
|
||
<div id="gameContainer">
|
||
<!-- Game will be injected here -->
|
||
</div>
|
||
|
||
<!-- Required game elements -->
|
||
<div id="game-area" style="display: none;"></div>
|
||
<div id="task-area" style="display: none;"></div>
|
||
<div id="current-task" style="display: none;"></div>
|
||
<div id="task-content" style="display: none;"></div>
|
||
<div id="task-image" style="display: none;"></div>
|
||
<div id="task-text" style="display: none;"></div>
|
||
<div id="controls-area" style="display: none;"></div>
|
||
<div id="game-controls" style="display: none;"></div>
|
||
|
||
<!-- Required game buttons -->
|
||
<button id="complete-btn" style="display: none;">Complete Task</button>
|
||
<button id="skip-btn" style="display: none;">Quit Training</button>
|
||
<button id="pause-btn" style="display: none;">Pause</button>
|
||
<button id="quit-btn" style="display: none;">Quit Training</button>
|
||
<button id="play-again-btn" style="display: none;">Play Again</button>
|
||
|
||
<!-- Game status elements -->
|
||
<div id="game-status" style="display: none;"></div>
|
||
<div id="timer-display" style="display: none;"></div>
|
||
<div id="xp-display" style="display: none;"></div>
|
||
<div id="task-counter" style="display: none;"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Core Scripts -->
|
||
<script>
|
||
// Only load Electron-specific scripts if in Electron environment
|
||
if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') {
|
||
console.log('🖥️ Electron environment detected - loading desktop scripts');
|
||
// Preload script is automatically loaded by Electron, no need to include here
|
||
} else {
|
||
console.log('🌐 Browser environment detected - skipping desktop scripts');
|
||
}
|
||
</script>
|
||
<script src="src/utils/desktop-file-manager.js"></script>
|
||
<script src="src/features/media/baseVideoPlayer.js"></script>
|
||
<script src="src/features/video/videoPlayerManager.js"></script>
|
||
<script src="src/features/media/videoLibrary.js"></script>
|
||
<script src="src/features/images/popupImageManager.js"></script>
|
||
<script src="src/features/audio/audioManager.js"></script>
|
||
<script src="src/features/tts/voiceManager.js"></script>
|
||
<script src="src/features/webcam/webcamManager.js"></script>
|
||
<script src="src/features/stats/playerStats.js"></script>
|
||
<script src="src/features/ui/flashMessageManager.js"></script>
|
||
|
||
<script src="src/features/tasks/interactiveTaskManager.js"></script>
|
||
|
||
<!-- Data Scripts -->
|
||
<script src="src/data/gameData.js"></script>
|
||
<script src="src/data/modes/trainingGameData.js"></script>
|
||
<script src="src/data/modes/humiliationGameData.js"></script>
|
||
<script src="src/data/modes/dressUpGameData.js"></script>
|
||
<script src="src/data/modes/enduranceGameData.js"></script>
|
||
<script src="src/data/gameDataManager.js"></script>
|
||
|
||
<!-- Core Game Scripts -->
|
||
<script src="src/core/gameModeManager.js"></script>
|
||
<script src="src/core/game.js"></script>
|
||
<!-- Note: main.js is Electron-specific and not needed in browser -->
|
||
|
||
<script>
|
||
// Training Academy Global Variables
|
||
let trainingVideoLibrary = [];
|
||
let trainingPhotoLibrary = [];
|
||
let currentTrainingVideo = null;
|
||
let currentVideoIndex = 0;
|
||
let selectedTrainingMode = null;
|
||
let videoOpacity = 0.3;
|
||
let isVideoPlaying = true;
|
||
let videoControlListenersInitialized = false;
|
||
|
||
// Scenario System Variables
|
||
let currentScenarioTask = null;
|
||
let currentScenarioStep = 'start';
|
||
|
||
// TTS Variables
|
||
let voiceManager = null;
|
||
let ttsEnabled = true;
|
||
let currentUtterance = null;
|
||
let ttsQueue = [];
|
||
|
||
// ===== TTS FUNCTIONS =====
|
||
|
||
// Initialize TTS system
|
||
async function initializeTTS() {
|
||
try {
|
||
if (window.VoiceManager) {
|
||
voiceManager = new window.VoiceManager();
|
||
await voiceManager.initialize();
|
||
|
||
const voiceInfo = voiceManager.getVoiceInfo();
|
||
console.log('🔊 TTS initialized:', voiceInfo.name, 'on', voiceInfo.platform);
|
||
|
||
updateTTSStatus(`Voice: ${voiceInfo.name}`);
|
||
|
||
// Test TTS with welcome message
|
||
if (ttsEnabled) {
|
||
setTimeout(() => {
|
||
speakText("Welcome to the Gooner Training Academy. Voice narration is ready.");
|
||
}, 1000);
|
||
}
|
||
|
||
} else {
|
||
console.warn('⚠️ VoiceManager not available');
|
||
updateTTSStatus('Voice unavailable');
|
||
document.getElementById('tts-toggle').disabled = true;
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ TTS initialization failed:', error);
|
||
updateTTSStatus('Voice error');
|
||
}
|
||
}
|
||
|
||
// Speak text with TTS
|
||
function speakText(text, options = {}) {
|
||
if (!ttsEnabled || !voiceManager || !text) return;
|
||
|
||
// Clean up text for better speech
|
||
const cleanText = text
|
||
.replace(/🎯|📋|🧘|🎭|📸|🔍|✅|❌|⚠️|🎉|🏆|📊|🎮|🎬|🪞|📷|🎨/g, '') // Remove emojis
|
||
.replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold markdown
|
||
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
||
.replace(/\s+/g, ' ') // Normalize whitespace
|
||
.trim();
|
||
|
||
if (!cleanText) return;
|
||
|
||
// Stop current speech if speaking
|
||
if (currentUtterance) {
|
||
voiceManager.stop();
|
||
}
|
||
|
||
console.log('🔊 Speaking:', cleanText.substring(0, 50) + '...');
|
||
|
||
const speechOptions = {
|
||
rate: options.rate || 0.9, // Slightly slower for better comprehension
|
||
pitch: options.pitch || 1.1, // Slightly higher pitch
|
||
volume: options.volume || 0.8,
|
||
onStart: () => {
|
||
updateTTSStatus('Speaking...');
|
||
},
|
||
onEnd: () => {
|
||
updateTTSStatus('Voice ready');
|
||
currentUtterance = null;
|
||
|
||
// Process next in queue
|
||
if (ttsQueue.length > 0) {
|
||
const next = ttsQueue.shift();
|
||
setTimeout(() => speakText(next.text, next.options), 500);
|
||
}
|
||
},
|
||
onError: (error) => {
|
||
console.error('🔊 TTS error:', error);
|
||
updateTTSStatus('Voice error');
|
||
currentUtterance = null;
|
||
}
|
||
};
|
||
|
||
currentUtterance = voiceManager.speak(cleanText, speechOptions);
|
||
}
|
||
|
||
// Queue text for TTS (useful for multiple announcements)
|
||
function queueTTS(text, options = {}) {
|
||
if (!ttsEnabled || !voiceManager) return;
|
||
|
||
if (currentUtterance) {
|
||
ttsQueue.push({ text, options });
|
||
} else {
|
||
speakText(text, options);
|
||
}
|
||
}
|
||
|
||
// Toggle TTS on/off
|
||
function toggleTTS() {
|
||
ttsEnabled = !ttsEnabled;
|
||
|
||
// Update header toggle button
|
||
const headerToggleBtn = document.getElementById('tts-toggle');
|
||
// Update sidebar toggle button
|
||
const sidebarToggleBtn = document.getElementById('sidebar-tts-toggle');
|
||
|
||
if (ttsEnabled) {
|
||
if (headerToggleBtn) {
|
||
headerToggleBtn.innerHTML = '🔊 Voice: ON';
|
||
headerToggleBtn.style.background = 'linear-gradient(135deg, #8B5CF6, #EC4899)';
|
||
}
|
||
if (sidebarToggleBtn) {
|
||
sidebarToggleBtn.innerHTML = '🔊 ON';
|
||
sidebarToggleBtn.style.background = 'rgba(139, 92, 246, 0.4)';
|
||
sidebarToggleBtn.style.borderColor = 'rgba(139, 92, 246, 0.6)';
|
||
}
|
||
updateTTSStatus('Voice enabled');
|
||
speakText('Voice narration enabled.');
|
||
} else {
|
||
if (headerToggleBtn) {
|
||
headerToggleBtn.innerHTML = '🔇 Voice: OFF';
|
||
headerToggleBtn.style.background = 'linear-gradient(135deg, #6B7280, #9CA3AF)';
|
||
}
|
||
if (sidebarToggleBtn) {
|
||
sidebarToggleBtn.innerHTML = '🔇 OFF';
|
||
sidebarToggleBtn.style.background = 'rgba(107, 114, 128, 0.4)';
|
||
sidebarToggleBtn.style.borderColor = 'rgba(107, 114, 128, 0.6)';
|
||
}
|
||
updateTTSStatus('Voice disabled');
|
||
stopTTS();
|
||
}
|
||
|
||
console.log('🔊 TTS toggled:', ttsEnabled ? 'ON' : 'OFF');
|
||
}
|
||
|
||
// Stop current TTS
|
||
function stopTTS() {
|
||
if (voiceManager) {
|
||
voiceManager.stop();
|
||
}
|
||
|
||
currentUtterance = null;
|
||
ttsQueue = [];
|
||
updateTTSStatus(ttsEnabled ? 'Voice ready' : 'Voice disabled');
|
||
}
|
||
|
||
// Update TTS status display
|
||
function updateTTSStatus(status) {
|
||
// Update header status
|
||
const headerStatusEl = document.getElementById('tts-status-text');
|
||
if (headerStatusEl) {
|
||
headerStatusEl.textContent = status;
|
||
}
|
||
|
||
// Update sidebar status
|
||
const sidebarStatusEl = document.getElementById('sidebar-tts-status');
|
||
if (sidebarStatusEl) {
|
||
sidebarStatusEl.textContent = status;
|
||
}
|
||
}
|
||
|
||
// Announce training mode selection
|
||
function announceModeSelection(modeName) {
|
||
if (ttsEnabled) {
|
||
speakText(`${modeName} selected. Prepare for your training session.`);
|
||
}
|
||
}
|
||
|
||
// Initialize sidebar TTS controls to match current state
|
||
function initializeSidebarTTSControls() {
|
||
const sidebarToggleBtn = document.getElementById('sidebar-tts-toggle');
|
||
const sidebarStatusEl = document.getElementById('sidebar-tts-status');
|
||
|
||
if (sidebarToggleBtn) {
|
||
if (ttsEnabled) {
|
||
sidebarToggleBtn.innerHTML = '🔊 ON';
|
||
sidebarToggleBtn.style.background = 'rgba(139, 92, 246, 0.4)';
|
||
sidebarToggleBtn.style.borderColor = 'rgba(139, 92, 246, 0.6)';
|
||
} else {
|
||
sidebarToggleBtn.innerHTML = '🔇 OFF';
|
||
sidebarToggleBtn.style.background = 'rgba(107, 114, 128, 0.4)';
|
||
sidebarToggleBtn.style.borderColor = 'rgba(107, 114, 128, 0.6)';
|
||
}
|
||
}
|
||
|
||
if (sidebarStatusEl) {
|
||
sidebarStatusEl.textContent = ttsEnabled ? 'Voice ready' : 'Voice disabled';
|
||
}
|
||
|
||
console.log('🔊 Sidebar TTS controls initialized');
|
||
}
|
||
|
||
// ===== END TTS FUNCTIONS =====
|
||
|
||
// Helper function to get format from file path
|
||
function getFormatFromPath(filePath) {
|
||
const extension = filePath.split('.').pop().toLowerCase();
|
||
return extension || 'unknown';
|
||
}
|
||
|
||
// Get available modes from GameModeManager
|
||
function getAvailableModes() {
|
||
// Show all available training modes
|
||
// Training Academy has multiple specialized modes
|
||
const allModes = {
|
||
'photography-studio': {
|
||
name: 'Photography Studio',
|
||
description: 'Dedicated webcam photography and dressing sessions',
|
||
icon: '📸'
|
||
},
|
||
'training-academy': {
|
||
name: 'Training Academy',
|
||
description: 'Structured training programs and challenges',
|
||
icon: '🎓'
|
||
},
|
||
'punishment-gauntlet': {
|
||
name: 'Punishment Gauntlet',
|
||
description: 'Face intense punishment and humiliation challenges',
|
||
icon: '⛓️'
|
||
},
|
||
'endurance-trials': {
|
||
name: 'Endurance Trials',
|
||
description: 'Test your limits with marathon sessions',
|
||
icon: '💪'
|
||
}
|
||
};
|
||
|
||
// Return all available training modes
|
||
return allModes;
|
||
}
|
||
|
||
// Initialize Video Library (similar to Quick Play)
|
||
async function initializeVideoLibrary() {
|
||
try {
|
||
console.log('🎬 Training Academy: Initializing video library...');
|
||
|
||
// Check if we're in Electron environment with proper API access
|
||
const isElectron = window.electronAPI && (
|
||
window.electronAPI.readVideoDirectory ||
|
||
window.electronAPI.readDirectory ||
|
||
window.electronAPI.getVideoFiles
|
||
);
|
||
|
||
if (!isElectron) {
|
||
console.log('🌐 Electron API not available - checking alternative methods...');
|
||
|
||
// Try to use the unified video library from desktop file manager if available
|
||
if (window.desktopFileManager && window.desktopFileManager.unifiedVideoLibrary) {
|
||
const unifiedLibrary = window.desktopFileManager.unifiedVideoLibrary;
|
||
trainingVideoLibrary = [...unifiedLibrary];
|
||
console.log(`🎬 Using unified video library: ${trainingVideoLibrary.length} videos`);
|
||
|
||
if (trainingVideoLibrary.length > 0) {
|
||
document.getElementById('videoLibraryStatus').innerHTML =
|
||
`<span style="color: var(--color-success);">✅ ${trainingVideoLibrary.length} videos loaded (unified library)</span>`;
|
||
// Don't auto-play video on setup screen
|
||
} else {
|
||
document.getElementById('videoLibraryStatus').innerHTML =
|
||
'<span style="color: var(--color-warning);">⚠️ No videos in unified library. Use Library management to add directories.</span>';
|
||
}
|
||
return;
|
||
}
|
||
|
||
document.getElementById('videoLibraryStatus').innerHTML =
|
||
'<span style="color: var(--color-warning);">⚠️ Video library unavailable - Electron API not accessible</span>';
|
||
return;
|
||
}
|
||
|
||
// Get linked video directories from localStorage
|
||
const linkedDirectories = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
|
||
console.log('📁 Linked video directories:', linkedDirectories);
|
||
|
||
if (linkedDirectories.length === 0) {
|
||
document.getElementById('videoLibraryStatus').innerHTML =
|
||
'<span style="color: var(--color-warning);">⚠️ No video directories linked. Use Library management to add directories.</span>';
|
||
return;
|
||
}
|
||
|
||
// Initialize desktop file manager
|
||
if (window.desktopFileManager && typeof window.desktopFileManager.init === 'function') {
|
||
await window.desktopFileManager.init();
|
||
}
|
||
|
||
trainingVideoLibrary = [];
|
||
|
||
// Scan each linked directory for videos
|
||
for (const directoryData of linkedDirectories) {
|
||
try {
|
||
// Handle both string paths and directory objects
|
||
const directoryPath = typeof directoryData === 'string' ? directoryData : directoryData.path;
|
||
|
||
if (!directoryPath) {
|
||
console.warn('⚠️ Invalid directory data:', directoryData);
|
||
continue;
|
||
}
|
||
|
||
console.log(`📂 Scanning directory: ${directoryPath}`);
|
||
|
||
// Use the same video scanning approach as Quick Play
|
||
const videoExtensions = /\.(mp4|webm|avi|mov|mkv|wmv|flv|m4v)$/i;
|
||
let files = [];
|
||
|
||
if (window.electronAPI.readVideoDirectory) {
|
||
files = await window.electronAPI.readVideoDirectory(directoryPath);
|
||
} else if (window.electronAPI.readVideoDirectoryRecursive) {
|
||
files = await window.electronAPI.readVideoDirectoryRecursive(directoryPath);
|
||
} else if (window.electronAPI.readDirectory) {
|
||
const allFiles = await window.electronAPI.readDirectory(directoryPath);
|
||
files = allFiles.filter(file => videoExtensions.test(file.name));
|
||
} else if (window.electronAPI.getVideoFiles) {
|
||
files = await window.electronAPI.getVideoFiles(directoryPath);
|
||
}
|
||
|
||
if (files && files.length > 0) {
|
||
console.log(`📹 Found ${files.length} videos in ${directoryPath}`);
|
||
|
||
// Add directory info to each video
|
||
files.forEach(file => {
|
||
trainingVideoLibrary.push({
|
||
name: file.name,
|
||
path: file.path,
|
||
fullPath: file.path,
|
||
size: file.size || 0,
|
||
duration: file.duration || 0,
|
||
directory: directoryPath,
|
||
dateAdded: new Date().toISOString(),
|
||
category: 'directory'
|
||
});
|
||
});
|
||
} else {
|
||
console.log(`📹 No videos found in ${directoryPath}`);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Error scanning directory ${directoryData}:`, error);
|
||
}
|
||
}
|
||
|
||
console.log(`🎬 Total videos loaded: ${trainingVideoLibrary.length}`);
|
||
|
||
// Update status display
|
||
if (trainingVideoLibrary.length > 0) {
|
||
document.getElementById('videoLibraryStatus').innerHTML =
|
||
`<span style="color: var(--color-success);">✅ ${trainingVideoLibrary.length} videos loaded</span>`;
|
||
|
||
// Don't auto-play video on setup screen
|
||
// Video will start when user clicks "Start Training"
|
||
} else {
|
||
document.getElementById('videoLibraryStatus').innerHTML =
|
||
'<span style="color: var(--color-danger);">❌ No videos found in linked directories</span>';
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error initializing video library:', error);
|
||
document.getElementById('videoLibraryStatus').innerHTML =
|
||
'<span style="color: var(--color-danger);">❌ Error loading video library</span>';
|
||
}
|
||
}
|
||
|
||
// Initialize Photo Library
|
||
async function initializePhotoLibrary() {
|
||
try {
|
||
console.log('📸 Training Academy: Initializing photo library...');
|
||
|
||
// Debug: Check what APIs are available
|
||
console.log('🔍 Available APIs:', {
|
||
electronAPI: !!window.electronAPI,
|
||
getImageFiles: window.electronAPI && typeof window.electronAPI.getImageFiles,
|
||
readDirectory: window.electronAPI && typeof window.electronAPI.readDirectory,
|
||
desktopFileManager: !!window.desktopFileManager
|
||
});
|
||
|
||
// Check if we're in Electron environment with proper API access
|
||
const isElectron = window.electronAPI && (
|
||
typeof window.electronAPI.getImageFiles === 'function' ||
|
||
typeof window.electronAPI.readDirectory === 'function'
|
||
);
|
||
|
||
if (!isElectron && !window.desktopFileManager) {
|
||
document.getElementById('photoLibraryStatus').innerHTML =
|
||
'<span style="color: var(--color-warning);">⚠️ Photo library unavailable - Electron API not accessible</span>';
|
||
return;
|
||
}
|
||
|
||
const linkedPhotoDirectories = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
|
||
const linkedIndividualImages = JSON.parse(localStorage.getItem('linkedIndividualImages') || '[]');
|
||
console.log('📁 Linked photo directories:', linkedPhotoDirectories);
|
||
console.log('📷 Linked individual images:', linkedIndividualImages);
|
||
|
||
if (linkedPhotoDirectories.length === 0 && linkedIndividualImages.length === 0) {
|
||
document.getElementById('photoLibraryStatus').innerHTML =
|
||
'<span style="color: var(--color-warning);">⚠️ No photo directories linked</span>';
|
||
return;
|
||
}
|
||
|
||
trainingPhotoLibrary = [];
|
||
let totalPhotos = 0;
|
||
|
||
// Process linked directories
|
||
for (const directoryData of linkedPhotoDirectories) {
|
||
try {
|
||
// Handle both string paths and directory objects
|
||
const directoryPath = typeof directoryData === 'string' ? directoryData : directoryData.path;
|
||
|
||
if (!directoryPath) {
|
||
console.warn('⚠️ Invalid photo directory data:', directoryData);
|
||
continue;
|
||
}
|
||
|
||
console.log(`📂 Scanning photo directory: ${directoryPath}`);
|
||
|
||
let photos = [];
|
||
|
||
// Try different Electron APIs for reading image files
|
||
if (window.electronAPI && window.electronAPI.getImageFiles) {
|
||
photos = await window.electronAPI.getImageFiles(directoryPath);
|
||
} else if (window.electronAPI && window.electronAPI.readDirectory) {
|
||
const allFiles = await window.electronAPI.readDirectory(directoryPath);
|
||
// Filter for image files
|
||
photos = allFiles.filter(file =>
|
||
/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name || file.filename)
|
||
);
|
||
} else if (window.desktopFileManager) {
|
||
// Fallback to desktop file manager
|
||
photos = window.desktopFileManager.getImagesFromDirectory(directoryPath) || [];
|
||
}
|
||
|
||
console.log(`📷 Found ${photos.length} photos in ${directoryPath}`);
|
||
|
||
const photosWithPath = photos.map(photo => ({
|
||
...photo,
|
||
directory: directoryPath,
|
||
fullPath: photo.path
|
||
}));
|
||
|
||
trainingPhotoLibrary.push(...photosWithPath);
|
||
totalPhotos += photos.length;
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Error scanning photo directory ${directoryData}:`, error);
|
||
}
|
||
}
|
||
|
||
// Process individual linked images
|
||
for (const imageData of linkedIndividualImages) {
|
||
try {
|
||
const imageWithPath = {
|
||
...imageData,
|
||
fullPath: imageData.path || imageData.filePath
|
||
};
|
||
trainingPhotoLibrary.push(imageWithPath);
|
||
totalPhotos++;
|
||
} catch (error) {
|
||
console.error(`❌ Error processing individual image ${imageData}:`, error);
|
||
}
|
||
}
|
||
|
||
console.log(`📸 Total photos loaded: ${totalPhotos} (${linkedPhotoDirectories.length} directories + ${linkedIndividualImages.length} individual images)`);
|
||
|
||
// Load previously captured photos from file system or localStorage
|
||
try {
|
||
// Try loading from file system first (Electron)
|
||
if (window.desktopFileManager && window.desktopFileManager.isElectron) {
|
||
const filePhotos = await window.desktopFileManager.loadCapturedPhotos();
|
||
if (filePhotos.length > 0) {
|
||
trainingPhotoLibrary.push(...filePhotos);
|
||
console.log(`📸 Loaded ${filePhotos.length} photos from file system`);
|
||
}
|
||
} else {
|
||
// Browser fallback - load from localStorage
|
||
const savedWebcamPhotos = JSON.parse(localStorage.getItem('trainingAcademyPhotos') || '[]');
|
||
if (savedWebcamPhotos.length > 0) {
|
||
trainingPhotoLibrary.push(...savedWebcamPhotos);
|
||
console.log(`📸 Loaded ${savedWebcamPhotos.length} previously captured photos from localStorage`);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.warn('⚠️ Failed to load saved photos:', error);
|
||
}
|
||
|
||
document.getElementById('photoLibraryStatus').innerHTML =
|
||
`<span style="color: var(--color-success);">✅ ${trainingPhotoLibrary.length} photos available</span>`;
|
||
|
||
// Show gallery button if there are photos
|
||
const totalPhotoCount = trainingPhotoLibrary.length;
|
||
if (totalPhotoCount > 0) {
|
||
document.getElementById('view-gallery-btn').style.display = 'inline-block';
|
||
|
||
// Update button text to indicate total photos
|
||
document.getElementById('view-gallery-btn').innerHTML =
|
||
`📸 View Gallery (${totalPhotoCount} photos)`;
|
||
|
||
console.log('📸 Gallery button updated. Total photos:', totalPhotoCount);
|
||
console.log('📸 Library photos:', trainingPhotoLibrary.length, 'Verification photos:', verificationPhotos.length, 'Main captured:', mainCapturedPhotos.length);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error initializing photo library:', error);
|
||
document.getElementById('photoLibraryStatus').innerHTML =
|
||
'<span style="color: var(--color-danger);">❌ Error loading photo library</span>';
|
||
}
|
||
}
|
||
|
||
// Start Background Video
|
||
async function startBackgroundVideo() {
|
||
if (trainingVideoLibrary.length === 0) return;
|
||
|
||
try {
|
||
const randomIndex = Math.floor(Math.random() * trainingVideoLibrary.length);
|
||
const selectedVideo = trainingVideoLibrary[randomIndex];
|
||
currentVideoIndex = randomIndex;
|
||
|
||
console.log(`🎬 Starting background video: ${selectedVideo.name}`);
|
||
|
||
const videoContainer = document.getElementById('trainingVideoContainer');
|
||
videoContainer.innerHTML = `
|
||
<video id="backgroundVideo" autoplay muted loop style="opacity: ${videoOpacity};">
|
||
<source src="file://${selectedVideo.fullPath}" type="video/mp4">
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
`;
|
||
|
||
const video = document.getElementById('backgroundVideo');
|
||
|
||
video.onloadstart = () => {
|
||
console.log('🎬 Video loading started');
|
||
};
|
||
|
||
video.onloadeddata = () => {
|
||
console.log('🎬 Video data loaded');
|
||
setupVideoControlListeners(); // Initialize control functionality
|
||
};
|
||
|
||
video.onloadedmetadata = () => {
|
||
console.log('🎬 Video metadata loaded');
|
||
// Update video info once metadata is available
|
||
setTimeout(() => {
|
||
updateVideoInfo();
|
||
updateVideoProgress();
|
||
|
||
// Ensure play/pause button state is correct
|
||
const playPauseBtn = document.getElementById('video-play-pause');
|
||
if (playPauseBtn) {
|
||
if (video.paused) {
|
||
playPauseBtn.textContent = '▶️';
|
||
playPauseBtn.title = 'Play';
|
||
} else {
|
||
playPauseBtn.textContent = '⏸️';
|
||
playPauseBtn.title = 'Pause';
|
||
}
|
||
console.log('🎮 Button state updated on metadata load:', video.paused ? 'Play' : 'Pause');
|
||
}
|
||
}, 100);
|
||
};
|
||
|
||
video.onplay = () => {
|
||
console.log('🎬 Video started playing');
|
||
const playPauseBtn = document.getElementById('video-play-pause');
|
||
if (playPauseBtn) {
|
||
playPauseBtn.textContent = '⏸️';
|
||
playPauseBtn.title = 'Pause';
|
||
}
|
||
};
|
||
|
||
video.onerror = (e) => {
|
||
console.error('❌ Video error:', e);
|
||
// Only skip to next video if we're in an active training session
|
||
const gameInterface = document.getElementById('gameInterface');
|
||
if (gameInterface && gameInterface.style.display !== 'none') {
|
||
skipToNextVideo(); // Try next video on error during active session
|
||
}
|
||
};
|
||
|
||
currentTrainingVideo = video;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error starting background video:', error);
|
||
}
|
||
}
|
||
|
||
// Video Control Functions
|
||
function skipToNextVideo() {
|
||
if (trainingVideoLibrary.length === 0) return;
|
||
|
||
currentVideoIndex = (currentVideoIndex + 1) % trainingVideoLibrary.length;
|
||
const nextVideo = trainingVideoLibrary[currentVideoIndex];
|
||
|
||
const video = document.getElementById('backgroundVideo');
|
||
if (video) {
|
||
console.log(`🎬 Switching from video paused state: ${video.paused}`);
|
||
video.src = `file://${nextVideo.fullPath}`;
|
||
console.log(`🎬 Switched to: ${nextVideo.name}`);
|
||
console.log(`🎬 After src change, video paused state: ${video.paused}`);
|
||
|
||
// Update video info and ensure controls work for the new video
|
||
video.addEventListener('loadedmetadata', function() {
|
||
updateVideoInfo();
|
||
updateVideoProgress();
|
||
refreshVideoControlState();
|
||
}, { once: true });
|
||
|
||
// Ensure button state updates when new video plays
|
||
video.addEventListener('play', function() {
|
||
refreshVideoControlState();
|
||
}, { once: true });
|
||
}
|
||
}
|
||
|
||
function toggleVideoOpacity() {
|
||
const opacityLevels = [0.1, 0.3, 0.5, 0.7];
|
||
const currentIndex = opacityLevels.indexOf(videoOpacity);
|
||
videoOpacity = opacityLevels[(currentIndex + 1) % opacityLevels.length];
|
||
|
||
const video = document.getElementById('backgroundVideo');
|
||
if (video) {
|
||
video.style.opacity = videoOpacity;
|
||
console.log(`🎬 Video opacity: ${(videoOpacity * 100)}%`);
|
||
}
|
||
}
|
||
|
||
function toggleVideoPlayback() {
|
||
const video = document.getElementById('backgroundVideo');
|
||
if (video) {
|
||
if (isVideoPlaying) {
|
||
video.pause();
|
||
isVideoPlaying = false;
|
||
console.log('⏸️ Video paused');
|
||
} else {
|
||
video.play();
|
||
isVideoPlaying = true;
|
||
console.log('▶️ Video resumed');
|
||
}
|
||
}
|
||
}
|
||
|
||
// Refresh Video Control State (for video changes)
|
||
function refreshVideoControlState() {
|
||
const video = document.getElementById('backgroundVideo');
|
||
const playPauseBtn = document.getElementById('video-play-pause');
|
||
|
||
if (video && playPauseBtn) {
|
||
if (video.paused) {
|
||
playPauseBtn.textContent = '▶️';
|
||
playPauseBtn.title = 'Play';
|
||
} else {
|
||
playPauseBtn.textContent = '⏸️';
|
||
playPauseBtn.title = 'Pause';
|
||
}
|
||
console.log('🎮 Control state refreshed:', video.paused ? 'Play' : 'Pause');
|
||
}
|
||
}
|
||
|
||
// Toggle Video Controls Panel
|
||
function toggleVideoControls() {
|
||
const content = document.getElementById('video-control-content');
|
||
const toggle = document.getElementById('video-control-toggle');
|
||
|
||
if (content.classList.contains('collapsed')) {
|
||
content.classList.remove('collapsed');
|
||
toggle.classList.remove('collapsed');
|
||
toggle.textContent = '▼';
|
||
} else {
|
||
content.classList.add('collapsed');
|
||
toggle.classList.add('collapsed');
|
||
toggle.textContent = '▶';
|
||
}
|
||
}
|
||
|
||
// Setup Complete Video Control System (from Quick Play)
|
||
function setupVideoControlListeners() {
|
||
// Prevent duplicate listener setup
|
||
if (videoControlListenersInitialized) {
|
||
console.log('🎮 Video control listeners already initialized, skipping...');
|
||
return;
|
||
}
|
||
|
||
const video = document.getElementById('backgroundVideo');
|
||
if (!video) {
|
||
console.log('⚠️ Background video element not found during control setup');
|
||
return;
|
||
}
|
||
|
||
console.log('🎮 Setting up video control listeners...');
|
||
|
||
// Play/Pause control
|
||
const playPauseBtn = document.getElementById('video-play-pause');
|
||
console.log('🎮 Play/pause button found:', !!playPauseBtn);
|
||
if (playPauseBtn) {
|
||
playPauseBtn.addEventListener('click', async () => {
|
||
const currentVideo = document.getElementById('backgroundVideo');
|
||
console.log('🎬 Video element found:', !!currentVideo);
|
||
console.log('🎬 Video paused state:', currentVideo ? currentVideo.paused : 'no video');
|
||
|
||
if (!currentVideo) {
|
||
console.log('No video element found');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (currentVideo.paused) {
|
||
await currentVideo.play();
|
||
playPauseBtn.textContent = '⏸️';
|
||
playPauseBtn.title = 'Pause';
|
||
console.log('▶️ Video resumed');
|
||
} else {
|
||
currentVideo.pause();
|
||
playPauseBtn.textContent = '▶️';
|
||
playPauseBtn.title = 'Play';
|
||
console.log('⏸️ Video paused');
|
||
}
|
||
} catch (error) {
|
||
console.error('Video playback error:', error);
|
||
}
|
||
});
|
||
} else {
|
||
console.error('❌ Play/pause button not found!');
|
||
}
|
||
|
||
// Rewind 10 seconds
|
||
const rewindBtn = document.getElementById('video-rewind');
|
||
if (rewindBtn) {
|
||
rewindBtn.addEventListener('click', () => {
|
||
const currentVideo = document.getElementById('backgroundVideo');
|
||
if (currentVideo && currentVideo.duration && isFinite(currentVideo.duration)) {
|
||
currentVideo.currentTime = Math.max(0, currentVideo.currentTime - 10);
|
||
console.log('⏪ Rewound 10 seconds');
|
||
} else {
|
||
console.log('Cannot rewind: video not ready');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Forward 10 seconds
|
||
const forwardBtn = document.getElementById('video-forward');
|
||
if (forwardBtn) {
|
||
forwardBtn.addEventListener('click', () => {
|
||
const currentVideo = document.getElementById('backgroundVideo');
|
||
if (currentVideo && currentVideo.duration && isFinite(currentVideo.duration)) {
|
||
currentVideo.currentTime = Math.min(currentVideo.duration, currentVideo.currentTime + 10);
|
||
console.log('⏩ Fast forwarded 10 seconds');
|
||
} else {
|
||
console.log('Cannot fast forward: video not ready');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Skip video
|
||
const skipBtn = document.getElementById('video-skip');
|
||
if (skipBtn) {
|
||
skipBtn.addEventListener('click', () => {
|
||
skipToNextVideo();
|
||
});
|
||
}
|
||
|
||
// Volume control
|
||
const volumeSlider = document.getElementById('video-volume');
|
||
const volumeDisplay = document.getElementById('video-volume-display');
|
||
if (volumeSlider && volumeDisplay) {
|
||
volumeSlider.addEventListener('input', (e) => {
|
||
const currentVideo = document.getElementById('backgroundVideo');
|
||
if (currentVideo) {
|
||
const volume = e.target.value / 100;
|
||
currentVideo.volume = volume;
|
||
volumeDisplay.textContent = e.target.value + '%';
|
||
console.log(`🔊 Volume set to ${e.target.value}% (${volume})`);
|
||
|
||
// Auto-mute when volume is 0
|
||
if (volume === 0) {
|
||
currentVideo.muted = true;
|
||
} else if (currentVideo.muted && volume > 0) {
|
||
currentVideo.muted = false;
|
||
}
|
||
} else {
|
||
console.warn('⚠️ No background video element found for volume control');
|
||
}
|
||
});
|
||
|
||
// Set initial volume when video loads
|
||
const initializeVolume = () => {
|
||
const currentVideo = document.getElementById('backgroundVideo');
|
||
if (currentVideo) {
|
||
currentVideo.volume = volumeSlider.value / 100;
|
||
volumeDisplay.textContent = volumeSlider.value + '%';
|
||
console.log(`🔊 Initial volume set to ${volumeSlider.value}%`);
|
||
}
|
||
};
|
||
|
||
// Try to set initial volume now and also when video loads
|
||
initializeVolume();
|
||
|
||
// Also set up a listener for when new videos are loaded
|
||
document.addEventListener('videoLoaded', initializeVolume);
|
||
}
|
||
|
||
// Playlist selection
|
||
const playlistSelect = document.getElementById('video-playlist-select');
|
||
if (playlistSelect) {
|
||
playlistSelect.addEventListener('change', (e) => {
|
||
const selectedPlaylist = e.target.value;
|
||
console.log('🎵 Playlist changed to:', selectedPlaylist);
|
||
// For now, just load a random video from current library
|
||
skipToNextVideo();
|
||
});
|
||
}
|
||
|
||
// Video progress and time updates
|
||
video.addEventListener('timeupdate', updateVideoProgress);
|
||
video.addEventListener('loadedmetadata', updateVideoInfo);
|
||
video.addEventListener('play', () => {
|
||
const playPauseBtn = document.getElementById('video-play-pause');
|
||
if (playPauseBtn) {
|
||
playPauseBtn.textContent = '⏸️';
|
||
playPauseBtn.title = 'Pause';
|
||
}
|
||
});
|
||
video.addEventListener('pause', () => {
|
||
const playPauseBtn = document.getElementById('video-play-pause');
|
||
if (playPauseBtn) {
|
||
playPauseBtn.textContent = '▶️';
|
||
playPauseBtn.title = 'Play';
|
||
}
|
||
});
|
||
|
||
// Initial updates
|
||
setTimeout(() => {
|
||
updateVideoInfo();
|
||
updateVideoProgress();
|
||
|
||
// Set initial play/pause button state
|
||
const initialPlayPauseBtn = document.getElementById('video-play-pause');
|
||
if (initialPlayPauseBtn && video) {
|
||
if (video.paused) {
|
||
initialPlayPauseBtn.textContent = '▶️';
|
||
initialPlayPauseBtn.title = 'Play';
|
||
} else {
|
||
initialPlayPauseBtn.textContent = '⏸️';
|
||
initialPlayPauseBtn.title = 'Pause';
|
||
}
|
||
console.log('🎮 Initial button state set:', video.paused ? 'Play' : 'Pause');
|
||
}
|
||
}, 100);
|
||
|
||
// Initialize sidebar TTS controls state
|
||
initializeSidebarTTSControls();
|
||
|
||
console.log('✅ Video control listeners set up successfully');
|
||
videoControlListenersInitialized = true;
|
||
}
|
||
|
||
function updateVideoProgress() {
|
||
const video = document.getElementById('backgroundVideo');
|
||
const progressFill = document.getElementById('progress-fill');
|
||
const videoTime = document.getElementById('video-time');
|
||
|
||
if (video && progressFill && videoTime && video.duration && isFinite(video.duration)) {
|
||
const progress = (video.currentTime / video.duration) * 100;
|
||
progressFill.style.width = isNaN(progress) ? '0%' : progress + '%';
|
||
|
||
const currentMinutes = Math.floor(video.currentTime / 60);
|
||
const currentSeconds = Math.floor(video.currentTime % 60);
|
||
const durationMinutes = Math.floor(video.duration / 60);
|
||
const durationSeconds = Math.floor(video.duration % 60);
|
||
|
||
videoTime.textContent = `${currentMinutes.toString().padStart(2, '0')}:${currentSeconds.toString().padStart(2, '0')} / ${durationMinutes.toString().padStart(2, '0')}:${durationSeconds.toString().padStart(2, '0')}`;
|
||
} else if (videoTime) {
|
||
videoTime.textContent = '00:00 / 00:00';
|
||
}
|
||
}
|
||
|
||
function updateVideoInfo() {
|
||
const video = document.getElementById('backgroundVideo');
|
||
const videoName = document.getElementById('current-video-name');
|
||
|
||
if (video && videoName) {
|
||
const videoPath = video.src || (video.querySelector('source') && video.querySelector('source').src);
|
||
if (videoPath) {
|
||
const fileName = videoPath.split('/').pop().split('\\').pop().split('.')[0];
|
||
const formattedName = fileName.replace(/[-_]/g, ' ');
|
||
// Capitalize each word
|
||
const displayName = formattedName.split(' ').map(word =>
|
||
word.charAt(0).toUpperCase() + word.slice(1)
|
||
).join(' ');
|
||
videoName.textContent = displayName;
|
||
} else {
|
||
videoName.textContent = 'No video loaded';
|
||
}
|
||
}
|
||
}
|
||
|
||
// Training Mode Selection
|
||
function setupTrainingModeSelection() {
|
||
const container = document.getElementById('trainingModeSelection');
|
||
const availableModes = getAvailableModes();
|
||
|
||
Object.entries(availableModes).forEach(([modeId, mode]) => {
|
||
const modeCard = document.createElement('div');
|
||
modeCard.className = 'training-mode-card';
|
||
modeCard.dataset.mode = modeId;
|
||
modeCard.onclick = () => selectTrainingMode(modeId);
|
||
|
||
modeCard.innerHTML = `
|
||
<div class="mode-icon">${mode.icon}</div>
|
||
<div class="mode-name">${mode.name}</div>
|
||
<div class="mode-description">${mode.description}</div>
|
||
`;
|
||
|
||
container.appendChild(modeCard);
|
||
});
|
||
}
|
||
|
||
function selectTrainingMode(modeId) {
|
||
// Remove previous selection
|
||
document.querySelectorAll('.training-mode-card').forEach(card => {
|
||
card.classList.remove('selected');
|
||
});
|
||
|
||
// Select new mode
|
||
document.querySelector(`[data-mode="${modeId}"]`).classList.add('selected');
|
||
selectedTrainingMode = modeId;
|
||
|
||
// Enable floating start button
|
||
const floatingStartBtn = document.getElementById('floating-academy-start-btn');
|
||
if (floatingStartBtn) {
|
||
floatingStartBtn.disabled = false;
|
||
}
|
||
|
||
const availableModes = getAvailableModes();
|
||
console.log(`🎯 Selected training mode: ${availableModes[modeId].name}`);
|
||
|
||
// TTS announcement
|
||
announceModeSelection(availableModes[modeId].name);
|
||
|
||
// Save the selected mode
|
||
const settings = loadAcademySettings();
|
||
settings.selectedMode = modeId;
|
||
saveAcademySettings(settings);
|
||
}
|
||
|
||
// Setup Screen Initialization and Event Handlers
|
||
function initializeAcademySetupScreen() {
|
||
console.log('🎓 Initializing academy setup screen...');
|
||
|
||
// Load saved settings
|
||
const savedSettings = loadAcademySettings();
|
||
|
||
// Video enable/disable
|
||
const enableVideoCheckbox = document.getElementById('academy-enable-video');
|
||
const videoOptions = document.getElementById('academy-video-options');
|
||
const videoOpacitySlider = document.getElementById('academy-video-opacity');
|
||
const videoOpacityValue = document.getElementById('academy-video-opacity-value');
|
||
|
||
if (enableVideoCheckbox && videoOptions) {
|
||
enableVideoCheckbox.checked = savedSettings.enableVideo;
|
||
videoOptions.style.display = savedSettings.enableVideo ? 'block' : 'none';
|
||
|
||
enableVideoCheckbox.addEventListener('change', (e) => {
|
||
const enabled = e.target.checked;
|
||
if (videoOptions) {
|
||
videoOptions.style.display = enabled ? 'block' : 'none';
|
||
}
|
||
|
||
// Save setting
|
||
const settings = loadAcademySettings();
|
||
settings.enableVideo = enabled;
|
||
saveAcademySettings(settings);
|
||
|
||
console.log(`🎬 Video ${enabled ? 'enabled' : 'disabled'}`);
|
||
});
|
||
}
|
||
|
||
if (videoOpacitySlider && videoOpacityValue) {
|
||
videoOpacitySlider.value = savedSettings.videoOpacity || 0.7;
|
||
videoOpacityValue.textContent = Math.round((savedSettings.videoOpacity || 0.7) * 100) + '%';
|
||
|
||
videoOpacitySlider.addEventListener('input', (e) => {
|
||
const opacity = e.target.value;
|
||
videoOpacityValue.textContent = Math.round(opacity * 100) + '%';
|
||
|
||
// Save setting
|
||
const settings = loadAcademySettings();
|
||
settings.videoOpacity = parseFloat(opacity);
|
||
saveAcademySettings(settings);
|
||
|
||
// Apply to video if playing
|
||
const videoContainer = document.getElementById('trainingVideoContainer');
|
||
if (videoContainer) {
|
||
videoContainer.style.opacity = opacity;
|
||
}
|
||
});
|
||
}
|
||
|
||
// TTS enable/disable
|
||
const enableTTSCheckbox = document.getElementById('academy-enable-tts');
|
||
const ttsOptions = document.getElementById('academy-tts-options');
|
||
const ttsVolumeSlider = document.getElementById('academy-tts-volume');
|
||
const ttsVolumeValue = document.getElementById('academy-tts-volume-value');
|
||
|
||
if (enableTTSCheckbox && ttsOptions) {
|
||
enableTTSCheckbox.checked = savedSettings.enableTTS;
|
||
ttsOptions.style.display = savedSettings.enableTTS ? 'block' : 'none';
|
||
|
||
enableTTSCheckbox.addEventListener('change', (e) => {
|
||
const enabled = e.target.checked;
|
||
if (ttsOptions) {
|
||
ttsOptions.style.display = enabled ? 'block' : 'none';
|
||
}
|
||
|
||
// Save setting
|
||
const settings = loadAcademySettings();
|
||
settings.enableTTS = enabled;
|
||
saveAcademySettings(settings);
|
||
|
||
console.log(`🔊 TTS ${enabled ? 'enabled' : 'disabled'}`);
|
||
});
|
||
}
|
||
|
||
if (ttsVolumeSlider && ttsVolumeValue) {
|
||
ttsVolumeSlider.value = savedSettings.ttsVolume || 80;
|
||
ttsVolumeValue.textContent = (savedSettings.ttsVolume || 80) + '%';
|
||
|
||
ttsVolumeSlider.addEventListener('input', (e) => {
|
||
const volume = e.target.value;
|
||
ttsVolumeValue.textContent = volume + '%';
|
||
|
||
// Save setting
|
||
const settings = loadAcademySettings();
|
||
settings.ttsVolume = parseInt(volume);
|
||
saveAcademySettings(settings);
|
||
});
|
||
}
|
||
|
||
// Floating start button
|
||
const floatingStartBtn = document.getElementById('floating-academy-start-btn');
|
||
if (floatingStartBtn) {
|
||
floatingStartBtn.disabled = !selectedTrainingMode; // Initially disabled until mode selected
|
||
|
||
floatingStartBtn.addEventListener('click', () => {
|
||
startTrainingSession();
|
||
});
|
||
}
|
||
|
||
console.log('✅ Academy setup screen initialized');
|
||
}
|
||
|
||
// Load Academy Settings from localStorage
|
||
function loadAcademySettings() {
|
||
const defaultSettings = {
|
||
enableVideo: true,
|
||
videoOpacity: 0.7,
|
||
enableTTS: true,
|
||
ttsVolume: 80,
|
||
selectedMode: null
|
||
};
|
||
|
||
try {
|
||
const saved = localStorage.getItem('trainingAcademySettings');
|
||
if (saved) {
|
||
return { ...defaultSettings, ...JSON.parse(saved) };
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading academy settings:', error);
|
||
}
|
||
|
||
return defaultSettings;
|
||
}
|
||
|
||
// Save Academy Settings to localStorage
|
||
function saveAcademySettings(settings) {
|
||
try {
|
||
localStorage.setItem('trainingAcademySettings', JSON.stringify(settings));
|
||
console.log('💾 Academy settings saved:', settings);
|
||
} catch (error) {
|
||
console.error('Error saving academy settings:', error);
|
||
}
|
||
}
|
||
|
||
// Apply Academy Settings before starting session
|
||
function applyAcademySettings() {
|
||
const settings = loadAcademySettings();
|
||
|
||
console.log('⚙️ Applying academy settings:', settings);
|
||
|
||
// Apply video settings
|
||
const videoContainer = document.getElementById('trainingVideoContainer');
|
||
if (videoContainer) {
|
||
if (settings.enableVideo) {
|
||
videoContainer.style.display = 'block';
|
||
videoContainer.style.opacity = settings.videoOpacity;
|
||
} else {
|
||
videoContainer.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// Apply TTS settings
|
||
if (!settings.enableTTS) {
|
||
// Disable TTS if setting is off
|
||
isTTSEnabled = false;
|
||
} else {
|
||
isTTSEnabled = true;
|
||
// Set TTS volume (would need to be implemented in voiceManager)
|
||
if (window.voiceManager && typeof window.voiceManager.setVolume === 'function') {
|
||
window.voiceManager.setVolume(settings.ttsVolume / 100);
|
||
}
|
||
}
|
||
|
||
// Flash messages and popup images are handled by their respective managers checking localStorage
|
||
|
||
console.log('✅ Academy settings applied');
|
||
}
|
||
|
||
// Start Training Session (Updated to use setup screen)
|
||
function startTrainingSession() {
|
||
if (!selectedTrainingMode) {
|
||
alert('Please select a training mode first!');
|
||
return;
|
||
}
|
||
|
||
console.log(`🚀 Starting training session: ${selectedTrainingMode}`);
|
||
|
||
// Apply settings before starting
|
||
applyAcademySettings();
|
||
|
||
// Get settings for TTS and video
|
||
const settings = loadAcademySettings();
|
||
|
||
// Start background video if enabled
|
||
if (settings.enableVideo && trainingVideoLibrary.length > 0) {
|
||
startBackgroundVideo();
|
||
}
|
||
|
||
// TTS announcement
|
||
if (settings.enableTTS) {
|
||
const availableModes = getAvailableModes();
|
||
const modeName = availableModes[selectedTrainingMode]?.name || selectedTrainingMode;
|
||
speakText(`Starting ${modeName} training session. Your journey begins now.`);
|
||
}
|
||
|
||
// Hide setup screen
|
||
const setupScreen = document.getElementById('academy-setup');
|
||
if (setupScreen) {
|
||
setupScreen.style.display = 'none';
|
||
}
|
||
|
||
// Hide library status
|
||
const libraryStatus = document.querySelector('.library-status');
|
||
if (libraryStatus) {
|
||
libraryStatus.style.display = 'none';
|
||
}
|
||
|
||
// Minimize header during training - keep all elements visible
|
||
const academyHeader = document.querySelector('.academy-header');
|
||
if (academyHeader) {
|
||
// Make header smaller and less prominent during training
|
||
academyHeader.style.padding = '0.5rem';
|
||
academyHeader.style.background = 'rgba(26, 26, 26, 0.7)';
|
||
}
|
||
|
||
// Show game interface
|
||
const gameInterface = document.getElementById('gameInterface');
|
||
gameInterface.style.display = 'block';
|
||
|
||
// Make video controls less prominent during game
|
||
const videoControls = document.getElementById('videoControlsOverlay');
|
||
if (videoControls) {
|
||
videoControls.style.opacity = '0.5';
|
||
}
|
||
|
||
// Initialize training-specific game mode
|
||
initializeTrainingGame();
|
||
}
|
||
|
||
// Initialize Training Game
|
||
function initializeTrainingGame() {
|
||
try {
|
||
console.log('🎓 Initializing Training Academy game...');
|
||
|
||
// Debug: Check if humiliation data is properly loaded
|
||
console.log('🔍 Debug: Checking humiliation data...');
|
||
console.log('🔍 window.humiliationGameData exists:', !!window.humiliationGameData);
|
||
if (window.humiliationGameData) {
|
||
console.log('🔍 Humiliation scenarios:', window.humiliationGameData.scenarios?.length || 0);
|
||
console.log('🔍 Humiliation scenario IDs:', window.humiliationGameData.scenarios?.map(s => s.id) || []);
|
||
}
|
||
|
||
// Ensure we have GameModeManager
|
||
if (!window.gameModeManager) {
|
||
console.error('❌ GameModeManager not available');
|
||
alert('Error: Game system not properly loaded. Please refresh the page.');
|
||
return;
|
||
}
|
||
|
||
// Set up game mode manager for training
|
||
window.gameModeManager.currentMode = selectedTrainingMode;
|
||
console.log(`🎯 Set GameModeManager to: ${selectedTrainingMode}`);
|
||
|
||
// Get scenarios from GameModeManager's scenario collections
|
||
let scenarioIds = [];
|
||
if (window.gameModeManager.scenarioCollections[selectedTrainingMode]) {
|
||
scenarioIds = window.gameModeManager.scenarioCollections[selectedTrainingMode];
|
||
console.log(`📋 Found scenario collection for ${selectedTrainingMode}:`, scenarioIds);
|
||
} else {
|
||
// Fallback scenario IDs for training-academy
|
||
scenarioIds = [
|
||
'scenario-training-regimen',
|
||
'scenario-creative-tasks',
|
||
'scenario-training-session',
|
||
'scenario-obedience-training'
|
||
];
|
||
console.log('📋 Using fallback scenario IDs:', scenarioIds);
|
||
}
|
||
|
||
// Load training tasks using GameModeManager
|
||
let trainingTasks = [];
|
||
try {
|
||
trainingTasks = window.gameModeManager.getScenarioModeTasks();
|
||
console.log(`📋 Loaded ${trainingTasks.length} tasks from GameModeManager`);
|
||
|
||
// Debug: Log the actual task structure
|
||
if (trainingTasks.length > 0) {
|
||
console.log('🔍 First training task structure:', trainingTasks[0]);
|
||
console.log('🔍 Task IDs:', trainingTasks.map(t => t.id));
|
||
console.log('🔍 Task types:', trainingTasks.map(t => t.interactiveType));
|
||
}
|
||
|
||
} catch (error) {
|
||
console.warn('⚠️ GameModeManager task loading failed:', error);
|
||
}
|
||
|
||
// Special handling for specific modes
|
||
if (selectedTrainingMode === 'punishment-gauntlet' && window.humiliationGameData && window.humiliationGameData.scenarios) {
|
||
console.log('🎯 Punishment Gauntlet mode detected - loading humiliation scenarios directly');
|
||
trainingTasks = window.humiliationGameData.scenarios.map(scenario => ({
|
||
id: scenario.id,
|
||
text: scenario.text,
|
||
difficulty: scenario.difficulty,
|
||
type: 'main',
|
||
interactiveType: scenario.interactiveType,
|
||
interactiveData: scenario.interactiveData,
|
||
isScenario: true
|
||
}));
|
||
console.log(`📋 Loaded ${trainingTasks.length} punishment scenarios directly from humiliation data`);
|
||
} else if (selectedTrainingMode === 'training-academy' && window.trainingGameData && window.trainingGameData.scenarios) {
|
||
console.log('🎓 Training Academy mode detected - loading training scenarios directly');
|
||
trainingTasks = window.trainingGameData.scenarios.map(scenario => ({
|
||
id: scenario.id,
|
||
text: scenario.text,
|
||
difficulty: scenario.difficulty,
|
||
type: 'main',
|
||
interactiveType: scenario.interactiveType,
|
||
interactiveData: scenario.interactiveData,
|
||
isScenario: true
|
||
}));
|
||
console.log(`📋 Loaded ${trainingTasks.length} training scenarios directly from training data`);
|
||
console.log('🔍 First scenario interactive data:', trainingTasks[0]?.interactiveData ? 'Present' : 'Missing');
|
||
} else if (selectedTrainingMode === 'photography-studio' && window.dressUpGameData && window.dressUpGameData.scenarios) {
|
||
console.log('📸 Photography Studio mode detected - loading dress-up scenarios directly');
|
||
trainingTasks = window.dressUpGameData.scenarios.map(scenario => ({
|
||
id: scenario.id,
|
||
text: scenario.text,
|
||
difficulty: scenario.difficulty,
|
||
type: 'main',
|
||
interactiveType: scenario.interactiveType,
|
||
interactiveData: scenario.interactiveData,
|
||
isScenario: true
|
||
}));
|
||
console.log(`📋 Loaded ${trainingTasks.length} photography scenarios directly from dress-up data`);
|
||
} else if (selectedTrainingMode === 'endurance-trials' && window.enduranceGameData && window.enduranceGameData.scenarios) {
|
||
console.log('💪 Endurance Trials mode detected - loading endurance scenarios directly');
|
||
trainingTasks = window.enduranceGameData.scenarios.map(scenario => ({
|
||
id: scenario.id,
|
||
text: scenario.text,
|
||
difficulty: scenario.difficulty,
|
||
type: 'main',
|
||
interactiveType: scenario.interactiveType,
|
||
interactiveData: scenario.interactiveData,
|
||
isScenario: true
|
||
}));
|
||
console.log(`📋 Loaded ${trainingTasks.length} endurance scenarios directly from endurance data`);
|
||
}
|
||
|
||
// Fallback for other modes or if GameModeManager failed
|
||
if (trainingTasks.length === 0) {
|
||
let fallbackData = null;
|
||
|
||
if (selectedTrainingMode === 'punishment-gauntlet' && window.humiliationGameData && window.humiliationGameData.scenarios) {
|
||
fallbackData = window.humiliationGameData.scenarios;
|
||
console.log(`📋 Using humiliation data fallback for punishment-gauntlet mode`);
|
||
} else if (selectedTrainingMode === 'photography-studio' && window.dressUpGameData && window.dressUpGameData.scenarios) {
|
||
fallbackData = window.dressUpGameData.scenarios;
|
||
console.log(`📋 Using dress-up data fallback for photography-studio mode`);
|
||
} else if (selectedTrainingMode === 'endurance-trials' && window.enduranceGameData && window.enduranceGameData.scenarios) {
|
||
fallbackData = window.enduranceGameData.scenarios;
|
||
console.log(`📋 Using endurance data fallback for endurance-trials mode`);
|
||
} else if (selectedTrainingMode === 'training-academy' && window.trainingGameData && window.trainingGameData.scenarios) {
|
||
// Only use training data scenarios if they exist and the mode is specifically training-academy
|
||
fallbackData = window.trainingGameData.scenarios;
|
||
console.log(`📋 Using training data fallback for training-academy mode`);
|
||
}
|
||
// Removed the generic fallback to prevent using wrong task types
|
||
|
||
if (fallbackData) {
|
||
trainingTasks = fallbackData.map(scenario => ({
|
||
id: scenario.id,
|
||
text: scenario.text,
|
||
difficulty: scenario.difficulty,
|
||
type: 'main',
|
||
interactiveType: scenario.interactiveType,
|
||
interactiveData: scenario.interactiveData,
|
||
isScenario: true
|
||
}));
|
||
console.log(`📋 Loaded ${trainingTasks.length} tasks from fallback data`);
|
||
}
|
||
}
|
||
|
||
// Check if we have any tasks to work with
|
||
if (trainingTasks.length === 0) {
|
||
console.error('❌ No training tasks available for mode:', selectedTrainingMode);
|
||
alert(`Error: No training scenarios found for ${selectedTrainingMode} mode. This training mode may not have content available yet.`);
|
||
|
||
// Return to mode selection instead of failing
|
||
returnToModeSelection();
|
||
return;
|
||
}
|
||
|
||
// Filter out any invalid tasks and ensure proper structure
|
||
const validTrainingTasks = trainingTasks.filter(task => {
|
||
const isValid = task && task.id && task.text && task.interactiveType;
|
||
|
||
// Also exclude the default focus-hold training tasks that shouldn't be in scenarios
|
||
const isUnwantedDefaultTask = task.id === 'edge-focus-training' ||
|
||
task.id === 'stroking-endurance-training' ||
|
||
task.id === 'extended-edging-session';
|
||
|
||
if (isUnwantedDefaultTask) {
|
||
console.warn('⚠️ Filtering out unwanted default task:', task.id);
|
||
return false;
|
||
}
|
||
|
||
if (!isValid) {
|
||
console.warn('⚠️ Filtering out invalid task:', task);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}).map(task => {
|
||
// Ensure each task has all required properties for the game engine
|
||
return {
|
||
...task,
|
||
// Required basic properties
|
||
id: task.id,
|
||
text: task.text || 'Training Scenario',
|
||
difficulty: task.difficulty || 'Medium',
|
||
type: task.type || 'main',
|
||
|
||
// Interactive properties
|
||
interactiveType: task.interactiveType,
|
||
interactiveData: task.interactiveData || {},
|
||
|
||
// Game engine expectations
|
||
isScenario: true,
|
||
image: task.image || null,
|
||
story: task.story || task.text,
|
||
|
||
// Ensure task is marked as available/valid
|
||
disabled: false,
|
||
completed: false
|
||
};
|
||
});
|
||
|
||
if (validTrainingTasks.length === 0) {
|
||
console.error('❌ No valid training tasks after filtering');
|
||
alert('Error: All training tasks are invalid. Please check task structure.');
|
||
return;
|
||
}
|
||
|
||
console.log(`✅ Using ${validTrainingTasks.length} valid training tasks`);
|
||
console.log('🔍 First valid task:', validTrainingTasks[0]);
|
||
trainingTasks = validTrainingTasks;
|
||
|
||
// Store training tasks globally for progression
|
||
window.originalTrainingTasks = validTrainingTasks;
|
||
console.log('💾 Stored training tasks for progression:', validTrainingTasks.length);
|
||
|
||
// Set up the game container with proper game interface
|
||
const gameContainer = document.getElementById('gameContainer');
|
||
gameContainer.innerHTML = `
|
||
<div id="main-container" class="container">
|
||
<div id="game-content">
|
||
<!-- Game will inject the full interface here -->
|
||
<div id="game-area"></div>
|
||
<div id="task-area"></div>
|
||
<div id="controls-area"></div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Initialize the game properly using the existing game system
|
||
setTimeout(() => {
|
||
if (window.game) {
|
||
// Set the game mode and tasks directly
|
||
window.game.gameState = window.game.gameState || {};
|
||
window.game.gameState.gameMode = 'complete-all'; // Use complete-all for scenarios
|
||
window.game.gameState.trainingMode = selectedTrainingMode;
|
||
window.game.gameState.isRunning = false; // Not started yet
|
||
|
||
// Override the task loading system at multiple levels
|
||
console.log('📋 Setting up training task overrides...');
|
||
|
||
// 1. Override GameDataManager's task loading for this session
|
||
if (window.gameDataManager) {
|
||
const originalGetTasksForMode = window.gameDataManager.getTasksForMode.bind(window.gameDataManager);
|
||
window.gameDataManager.getTasksForMode = function(mode) {
|
||
if (mode === 'standard' || mode === selectedTrainingMode) {
|
||
console.log(`🎓 Overriding getTasksForMode(${mode}) with training tasks`);
|
||
return trainingTasks;
|
||
}
|
||
return originalGetTasksForMode(mode);
|
||
};
|
||
}
|
||
|
||
// 2. Override GameModeManager's task loading
|
||
if (window.gameModeManager) {
|
||
const originalGetTasksForMode = window.gameModeManager.getTasksForMode.bind(window.gameModeManager);
|
||
window.gameModeManager.getTasksForMode = function() {
|
||
console.log('🎓 Overriding GameModeManager.getTasksForMode() with training tasks');
|
||
return trainingTasks;
|
||
};
|
||
}
|
||
|
||
// 3. Set the training tasks directly in gameData
|
||
if (window.gameData) {
|
||
// Backup original tasks
|
||
window.gameData.originalMainTasks = window.gameData.originalMainTasks || [...(window.gameData.mainTasks || [])];
|
||
// Set training tasks as the active tasks
|
||
window.gameData.mainTasks = trainingTasks;
|
||
console.log('📋 Set training tasks as active game data');
|
||
|
||
// Create a robust getter/setter to prevent unwanted task replacement
|
||
const tasksList = [...trainingTasks];
|
||
Object.defineProperty(window.gameData, 'mainTasks', {
|
||
get: function() {
|
||
return this._trainingTasks || tasksList;
|
||
},
|
||
set: function(value) {
|
||
// Filter out unwanted default tasks if they somehow get added
|
||
if (Array.isArray(value)) {
|
||
const filteredTasks = value.filter(task =>
|
||
task.id !== 'edge-focus-training' &&
|
||
task.id !== 'stroking-endurance-training' &&
|
||
task.id !== 'extended-edging-session'
|
||
);
|
||
this._trainingTasks = filteredTasks.length > 0 ? filteredTasks : tasksList;
|
||
console.log('🛡️ Training Academy: Filtered tasks, keeping', this._trainingTasks.length, 'valid tasks');
|
||
} else {
|
||
this._trainingTasks = tasksList;
|
||
}
|
||
}
|
||
});
|
||
|
||
// Initialize with training tasks
|
||
window.gameData.mainTasks = trainingTasks;
|
||
}
|
||
|
||
// 3.5. Override interactive task display to prevent UI errors
|
||
if (window.game && window.game.interactiveTaskManager) {
|
||
const originalDisplayInteractiveTask = window.game.interactiveTaskManager.displayInteractiveTask.bind(window.game.interactiveTaskManager);
|
||
window.game.interactiveTaskManager.displayInteractiveTask = function(task) {
|
||
console.log('🎯 Training Academy: Custom interactive task display called - skipping');
|
||
// Skip interactive tasks in training academy
|
||
return Promise.resolve();
|
||
};
|
||
console.log('✅ Interactive task manager override applied');
|
||
} else {
|
||
console.log('⚠️ Interactive task manager not available for override');
|
||
}
|
||
|
||
// 3.6. Override completeTask to prevent errors during training academy
|
||
if (window.game) {
|
||
const originalCompleteTask = window.game.completeTask.bind(window.game);
|
||
window.game.completeTask = function() {
|
||
console.log('🎓 Training Academy: Preventing completeTask call during training');
|
||
// In training academy, task completion is handled by scenario flow
|
||
// Don't call the main game's task completion logic
|
||
return;
|
||
};
|
||
console.log('✅ CompleteTask override applied for training academy');
|
||
}
|
||
|
||
// 4. Override screen management to handle missing elements
|
||
const originalShowScreen = window.game.showScreen.bind(window.game);
|
||
window.game.showScreen = function(screenId) {
|
||
console.log(`🖥️ Training Academy: Attempting to show screen: ${screenId}`);
|
||
const element = document.getElementById(screenId);
|
||
if (!element) {
|
||
console.log(`⚠️ Training Academy: Screen ${screenId} not found, using game container instead`);
|
||
return; // Skip showing missing screens
|
||
}
|
||
return originalShowScreen(screenId);
|
||
};
|
||
|
||
// 5. Override task completion and game ending logic
|
||
const originalLoadMainTask = window.game.loadMainTask.bind(window.game);
|
||
window.game.loadMainTask = function() {
|
||
console.log('🎓 Training Academy: Custom loadMainTask called');
|
||
|
||
// Check if we have training tasks available
|
||
if (this.gameData && this.gameData.mainTasks && this.gameData.mainTasks.length > 0) {
|
||
console.log(`📋 Found ${this.gameData.mainTasks.length} training tasks available`);
|
||
|
||
// Validate the first task before proceeding
|
||
const firstTask = this.gameData.mainTasks[0];
|
||
if (!firstTask || !firstTask.text) {
|
||
console.log('❌ First task is invalid, skipping loadMainTask');
|
||
return false;
|
||
}
|
||
|
||
return originalLoadMainTask.call(this);
|
||
} else {
|
||
console.log('⚠️ No tasks available, preventing game end');
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// Override the task selection to ensure proper task structure
|
||
const originalSetCurrentTask = window.game.setCurrentTask ? window.game.setCurrentTask.bind(window.game) : null;
|
||
if (originalSetCurrentTask) {
|
||
window.game.setCurrentTask = function(task) {
|
||
console.log('🎯 Training Academy: Setting current task:', task);
|
||
|
||
if (!task || !task.text) {
|
||
console.log('❌ Invalid task provided to setCurrentTask');
|
||
return;
|
||
}
|
||
|
||
return originalSetCurrentTask.call(this, task);
|
||
};
|
||
}
|
||
|
||
// Override any method that sets currentTask to null
|
||
if (window.game.gameState) {
|
||
const originalGameState = window.game.gameState;
|
||
const currentTaskValue = originalGameState.currentTask;
|
||
|
||
Object.defineProperty(window.game.gameState, 'currentTask', {
|
||
get: function() {
|
||
return this._currentTask;
|
||
},
|
||
set: function(value) {
|
||
console.log('🎯 Training Academy: Setting gameState.currentTask:', value);
|
||
if (value && !value.text) {
|
||
console.log('❌ Preventing assignment of task without text property');
|
||
return;
|
||
}
|
||
this._currentTask = value;
|
||
}
|
||
});
|
||
|
||
// Initialize with current value
|
||
originalGameState._currentTask = currentTaskValue;
|
||
}
|
||
|
||
// 6. Override game ending to prevent immediate completion
|
||
const originalEndGame = window.game.endGame.bind(window.game);
|
||
window.game.endGame = function() {
|
||
console.log('🎓 Training Academy: Game end prevented - continuing training session');
|
||
// Don't actually end the game in training mode
|
||
return;
|
||
};
|
||
|
||
// 7. Override task display for training academy UI
|
||
const originalDisplayCurrentTask = window.game.displayCurrentTask.bind(window.game);
|
||
window.game.displayCurrentTask = function() {
|
||
console.log('🎓 Training Academy: Custom displayCurrentTask called');
|
||
console.log('📋 Current task state:', this.currentTask);
|
||
console.log('📋 Game state current task:', this.gameState?.currentTask);
|
||
|
||
// Check both possible task locations
|
||
const task = this.currentTask || this.gameState?.currentTask;
|
||
|
||
if (!task || !task.text) {
|
||
console.log('⚠️ No valid task available or missing text property');
|
||
// Show training academy specific message
|
||
const gameContainer = document.getElementById('game-container');
|
||
if (gameContainer) {
|
||
gameContainer.innerHTML = `
|
||
<div class="training-status">
|
||
<h3>🎓 Training Academy</h3>
|
||
<p>Select a training mode to begin your session.</p>
|
||
<button onclick="selectMode('training-academy')" class="mode-btn">Start Training</button>
|
||
</div>
|
||
`;
|
||
}
|
||
return; // NEVER call original method with invalid task
|
||
}
|
||
|
||
// For valid tasks, display in training academy format
|
||
const gameContainer = document.getElementById('game-container');
|
||
console.log('🎮 Game container found:', !!gameContainer);
|
||
if (gameContainer) {
|
||
// Check if it's an interactive task
|
||
const isInteractive = task.interactiveType && task.interactiveType !== 'none';
|
||
const taskTypeLabel = isInteractive ? `Interactive: ${task.interactiveType}` : task.type || 'General';
|
||
|
||
console.log('🎯 Displaying task in training academy UI...');
|
||
console.log('🎯 Task is interactive:', isInteractive);
|
||
console.log('🎯 Interactive data:', task.interactiveData);
|
||
|
||
// Handle different interactive types
|
||
if (task.interactiveType === 'scenario-adventure' && task.interactiveData) {
|
||
displayScenarioAdventure(gameContainer, task);
|
||
} else if (task.interactiveType === 'focus-hold') {
|
||
console.log('🧘 Detected focus-hold task - using interactive focus system');
|
||
displayFocusHoldTask(gameContainer, task);
|
||
} else {
|
||
// Standard task display
|
||
gameContainer.innerHTML = `
|
||
<div class="training-task">
|
||
<h3>🎯 Training Task ${task.id || 'Unknown'}</h3>
|
||
<p><strong>Difficulty:</strong> ${task.difficulty || 'Standard'}</p>
|
||
<p><strong>Type:</strong> ${taskTypeLabel}</p>
|
||
${isInteractive ? '<p><strong>Mode:</strong> Interactive Training</p>' : ''}
|
||
<div class="task-content">
|
||
${task.text}
|
||
</div>
|
||
${task.story ? `
|
||
<div class="task-story">
|
||
<p><strong>Instructions:</strong> ${task.story}</p>
|
||
</div>
|
||
` : ''}
|
||
${isInteractive ? `
|
||
<div class="interactive-notice">
|
||
<p>🎮 This is an interactive training scenario. Follow the instructions above to proceed.</p>
|
||
</div>
|
||
` : ''}
|
||
<div class="training-controls">
|
||
<button onclick="completeTrainingTask()" class="complete-btn">Complete Task</button>
|
||
<button onclick="showQuitConfirmation()" class="next-btn">Quit Training</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// TTS narration for the task
|
||
setTimeout(() => {
|
||
let narrationText = `New training task. ${task.text}`;
|
||
if (task.story) {
|
||
narrationText += ` Instructions: ${task.story}`;
|
||
}
|
||
if (isInteractive) {
|
||
narrationText += ' This is an interactive scenario.';
|
||
}
|
||
speakText(narrationText);
|
||
}, 500);
|
||
}
|
||
console.log('✅ Task UI updated in game container');
|
||
return;
|
||
} else {
|
||
console.log('❌ Game container not found - creating fallback display');
|
||
// Try to create a temporary display in the body
|
||
const fallbackContainer = document.querySelector('.game-content') || document.body;
|
||
|
||
// Remove any existing task displays first
|
||
const existingDisplays = document.querySelectorAll('#training-task-display');
|
||
existingDisplays.forEach(display => display.remove());
|
||
console.log('🧹 Removed', existingDisplays.length, 'existing task displays');
|
||
|
||
// Check if it's a scenario adventure
|
||
if (task.interactiveType === 'scenario-adventure' && task.interactiveData) {
|
||
console.log('🎭 Detected scenario adventure in fallback - using scenario display');
|
||
const tempDiv = document.createElement('div');
|
||
tempDiv.id = 'training-task-display';
|
||
tempDiv.style.cssText = 'position: fixed; top: 100px; left: 50%; transform: translateX(-50%); z-index: 9999; width: 90%; max-width: 1000px; max-height: calc(100vh - 120px); overflow-y: auto;';
|
||
fallbackContainer.appendChild(tempDiv);
|
||
displayScenarioAdventure(tempDiv, task);
|
||
console.log('✅ Scenario adventure fallback display created');
|
||
return;
|
||
}
|
||
|
||
const tempDiv = document.createElement('div');
|
||
tempDiv.id = 'training-task-display';
|
||
tempDiv.style.cssText = 'position: fixed; top: 20%; left: 50%; transform: translateX(-50%); z-index: 9999; width: 80%; max-width: 800px;';
|
||
|
||
// Check if it's an interactive task
|
||
const isInteractive = task.interactiveType && task.interactiveType !== 'none';
|
||
const taskTypeLabel = isInteractive ? `Interactive: ${task.interactiveType}` : task.type || 'General';
|
||
|
||
tempDiv.innerHTML = `
|
||
<div class="training-task">
|
||
<h3>🎯 Training Task ${task.id || 'Unknown'}</h3>
|
||
<p><strong>Difficulty:</strong> ${task.difficulty || 'Standard'}</p>
|
||
<p><strong>Type:</strong> ${taskTypeLabel}</p>
|
||
${isInteractive ? '<p><strong>Mode:</strong> Interactive Training</p>' : ''}
|
||
<div class="task-content">
|
||
${task.text}
|
||
</div>
|
||
${task.story ? `
|
||
<div class="task-story">
|
||
<p><strong>Instructions:</strong> ${task.story}</p>
|
||
</div>
|
||
` : ''}
|
||
${isInteractive ? `
|
||
<div class="interactive-notice">
|
||
<p>🎮 This is an interactive training scenario. Follow the instructions above to proceed.</p>
|
||
</div>
|
||
` : ''}
|
||
<div class="training-controls">
|
||
<button onclick="completeTrainingTask()" class="complete-btn">Complete Task</button>
|
||
<button onclick="showQuitConfirmation()" class="next-btn">Quit Training</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
fallbackContainer.appendChild(tempDiv);
|
||
console.log('✅ Fallback task display created and added to DOM');
|
||
console.log('🎯 Display element:', tempDiv);
|
||
return;
|
||
}
|
||
|
||
// Only call original if we have a valid task with text
|
||
if (task && task.text) {
|
||
// Ensure gameState.currentTask is set properly before calling original
|
||
if (!this.gameState.currentTask) {
|
||
this.gameState.currentTask = task;
|
||
}
|
||
return originalDisplayCurrentTask.call(this);
|
||
}
|
||
};
|
||
|
||
// Show the game interface elements that are needed
|
||
const elementsToShow = ['game-area', 'task-area', 'current-task', 'controls-area', 'game-controls'];
|
||
elementsToShow.forEach(id => {
|
||
const element = document.getElementById(id);
|
||
if (element) element.style.display = 'block';
|
||
});
|
||
|
||
// Bypass the normal startGame flow and go directly to task loading
|
||
console.log('🎮 Starting training game with custom flow...');
|
||
try {
|
||
// Set up game state manually
|
||
window.game.gameState.isRunning = true;
|
||
window.game.gameState.isPaused = false;
|
||
window.game.gameState.startTime = Date.now();
|
||
window.game.gameState.xp = 0;
|
||
|
||
// Initialize scenario XP tracking
|
||
if (window.game.initializeScenarioXp) {
|
||
window.game.initializeScenarioXp();
|
||
console.log('📊 Scenario XP system initialized');
|
||
}
|
||
|
||
// Show status bar and start XP tracking
|
||
showScenarioStatusBar();
|
||
startScenarioXPTracking();
|
||
|
||
// Load the first training task directly
|
||
if (trainingTasks.length > 0) {
|
||
console.log('<27> Loading first training task directly...');
|
||
window.game.currentTaskIndex = 0;
|
||
window.game.currentTask = trainingTasks[0];
|
||
window.game.displayCurrentTask();
|
||
console.log('✅ Training task loaded successfully');
|
||
} else {
|
||
throw new Error('No training tasks available to load');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error in custom training flow:', error);
|
||
|
||
// Final fallback: Try the standard game start
|
||
console.log('🔄 Falling back to standard game start...');
|
||
try {
|
||
window.game.startGame();
|
||
console.log('✅ Standard game start succeeded');
|
||
} catch (e2) {
|
||
console.error('❌ Standard game start also failed:', e2);
|
||
alert('Error: Unable to start training session. Please refresh and try again.');
|
||
}
|
||
}
|
||
|
||
} else {
|
||
console.error('❌ Game instance not available');
|
||
alert('Error: Game engine not loaded properly.');
|
||
}
|
||
}, 500);
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error initializing training game:', error);
|
||
alert('Error initializing training session. Please refresh the page and try again.');
|
||
}
|
||
}
|
||
|
||
// Training task button functions
|
||
function completeTrainingTask() {
|
||
console.log('✅ Training task completed by user');
|
||
|
||
// TTS feedback
|
||
speakText('Task completed successfully. Moving to next challenge.');
|
||
|
||
try {
|
||
// Mark current task as completed
|
||
if (window.game && window.game.gameState && window.game.gameState.currentTask) {
|
||
console.log('📋 Marking current task as completed');
|
||
window.game.gameState.currentTask.completed = true;
|
||
}
|
||
|
||
// Load next task
|
||
loadNextTrainingTask();
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error completing training task:', error);
|
||
// Still try to load next task
|
||
loadNextTrainingTask();
|
||
}
|
||
}
|
||
|
||
function loadNextTrainingTask() {
|
||
console.log('➡️ Loading next training task...');
|
||
|
||
try {
|
||
// Get current training tasks from multiple possible locations
|
||
let trainingTasks = window.game?.gameData?.mainTasks || [];
|
||
|
||
// If not found there, try the original training tasks we stored
|
||
if (trainingTasks.length === 0 && window.originalTrainingTasks) {
|
||
trainingTasks = window.originalTrainingTasks;
|
||
console.log('📋 Using stored original training tasks:', trainingTasks.length);
|
||
}
|
||
|
||
console.log('📋 Available training tasks:', trainingTasks.length);
|
||
console.log('📋 Task IDs:', trainingTasks.map(t => t.id));
|
||
|
||
if (trainingTasks.length === 0) {
|
||
console.log('⚠️ No training tasks found in any location');
|
||
showTrainingComplete();
|
||
return;
|
||
}
|
||
|
||
// Find current task index
|
||
const currentTask = window.game?.gameState?.currentTask || window.game?.currentTask;
|
||
let currentIndex = -1;
|
||
|
||
if (currentTask) {
|
||
currentIndex = trainingTasks.findIndex(task => task.id === currentTask.id);
|
||
console.log('📋 Current task index:', currentIndex, 'for task:', currentTask.id);
|
||
}
|
||
|
||
// Get next task
|
||
const nextIndex = currentIndex + 1;
|
||
console.log('📋 Next task index:', nextIndex, 'of', trainingTasks.length);
|
||
|
||
if (nextIndex < trainingTasks.length) {
|
||
const nextTask = trainingTasks[nextIndex];
|
||
console.log('📋 Loading next task:', nextTask.id);
|
||
|
||
// Set as current task in both locations
|
||
window.game.gameState.currentTask = nextTask;
|
||
window.game.currentTask = nextTask;
|
||
|
||
// Display the task
|
||
window.game.displayCurrentTask();
|
||
|
||
} else {
|
||
console.log('🎉 All training tasks completed!');
|
||
showTrainingComplete();
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error loading next training task:', error);
|
||
alert('Error loading next task. Please restart the training session.');
|
||
}
|
||
}
|
||
|
||
function showTrainingComplete() {
|
||
console.log('🎓 Training session completed');
|
||
|
||
// Remove any existing task displays
|
||
const existing = document.getElementById('training-task-display');
|
||
if (existing) existing.remove();
|
||
|
||
// Clear the game container
|
||
const gameContainer = document.getElementById('game-container');
|
||
if (gameContainer) gameContainer.innerHTML = '';
|
||
|
||
// Return to mode selection by resetting the interface
|
||
returnToModeSelection();
|
||
}
|
||
|
||
function showQuitConfirmation() {
|
||
console.log('⚠️ Showing quit training confirmation...');
|
||
|
||
// Get dynamic content based on selected training mode
|
||
const dialogContent = getQuipyDialogContent();
|
||
|
||
// Create confirmation overlay
|
||
const confirmOverlay = document.createElement('div');
|
||
confirmOverlay.id = 'quit-confirmation-overlay';
|
||
confirmOverlay.innerHTML = `
|
||
<div class="confirmation-backdrop"></div>
|
||
<div class="confirmation-modal">
|
||
<div class="confirmation-header">
|
||
<div class="confirmation-icon">${dialogContent.icon}</div>
|
||
<h3>${dialogContent.title}</h3>
|
||
</div>
|
||
|
||
<div class="confirmation-content">
|
||
<p>${dialogContent.message}</p>
|
||
<div class="confirmation-warning">
|
||
<span class="warning-icon">${dialogContent.warningIcon}</span>
|
||
<span>${dialogContent.warning}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="confirmation-actions">
|
||
<button class="confirm-btn quit-btn" onclick="confirmQuitTraining()">
|
||
<span class="btn-icon">${dialogContent.quitIcon || '🚪'}</span>
|
||
<span>${dialogContent.quitText || 'Quit Training'}</span>
|
||
</button>
|
||
<button class="confirm-btn continue-btn" onclick="cancelQuitTraining()">
|
||
<span class="btn-icon">${dialogContent.continueIcon || '💪'}</span>
|
||
<span>${dialogContent.continueText || 'Continue Training'}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Add styles
|
||
confirmOverlay.innerHTML += `
|
||
<style>
|
||
#quit-confirmation-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
z-index: 10000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
animation: fadeIn 0.3s ease-out;
|
||
}
|
||
|
||
.confirmation-backdrop {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
backdrop-filter: blur(5px);
|
||
}
|
||
|
||
.confirmation-modal {
|
||
position: relative;
|
||
background: linear-gradient(145deg, #1a1a1a, #2d1b3d);
|
||
border: 2px solid var(--color-danger);
|
||
border-radius: 20px;
|
||
padding: 2rem;
|
||
max-width: 450px;
|
||
width: 90%;
|
||
box-shadow: 0 20px 40px rgba(255, 23, 68, 0.3), 0 0 20px rgba(255, 23, 68, 0.1);
|
||
animation: modalSlideIn 0.4s ease-out;
|
||
text-align: center;
|
||
}
|
||
|
||
.confirmation-header {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.confirmation-icon {
|
||
font-size: 3rem;
|
||
margin-bottom: 0.5rem;
|
||
filter: drop-shadow(0 0 10px rgba(255, 193, 7, 0.5));
|
||
}
|
||
|
||
.confirmation-header h3 {
|
||
color: var(--color-danger);
|
||
font-size: 1.5rem;
|
||
font-weight: 700;
|
||
margin: 0;
|
||
text-shadow: 0 0 10px rgba(255, 23, 68, 0.3);
|
||
}
|
||
|
||
.confirmation-content {
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.confirmation-content p {
|
||
color: var(--text-secondary);
|
||
font-size: 1.1rem;
|
||
margin-bottom: 1rem;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.confirmation-warning {
|
||
background: linear-gradient(135deg, rgba(255, 193, 7, 0.1), rgba(255, 152, 0, 0.1));
|
||
border: 1px solid rgba(255, 193, 7, 0.3);
|
||
border-radius: 10px;
|
||
padding: 0.75rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
font-size: 0.9rem;
|
||
color: var(--color-warning);
|
||
}
|
||
|
||
.warning-icon {
|
||
font-size: 1.2rem;
|
||
filter: drop-shadow(0 0 5px rgba(255, 193, 7, 0.5));
|
||
}
|
||
|
||
.confirmation-actions {
|
||
display: flex;
|
||
gap: 1rem;
|
||
justify-content: center;
|
||
}
|
||
|
||
.confirm-btn {
|
||
background: linear-gradient(135deg, #2d1b3d, #1a1a1a);
|
||
border: 2px solid;
|
||
border-radius: 15px;
|
||
padding: 0.75rem 1.5rem;
|
||
color: white;
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
transition: all 0.3s ease;
|
||
min-width: 150px;
|
||
justify-content: center;
|
||
}
|
||
|
||
.quit-btn {
|
||
border-color: var(--color-danger);
|
||
box-shadow: 0 0 15px rgba(255, 23, 68, 0.3);
|
||
}
|
||
|
||
.quit-btn:hover {
|
||
background: linear-gradient(135deg, #ff1744, #d81b60);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 20px rgba(255, 23, 68, 0.5);
|
||
}
|
||
|
||
.continue-btn {
|
||
border-color: var(--color-success);
|
||
box-shadow: 0 0 15px rgba(76, 175, 80, 0.3);
|
||
}
|
||
|
||
.continue-btn:hover {
|
||
background: linear-gradient(135deg, #4caf50, #388e3c);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 20px rgba(76, 175, 80, 0.5);
|
||
}
|
||
|
||
.btn-icon {
|
||
font-size: 1.1rem;
|
||
filter: drop-shadow(0 0 5px currentColor);
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
@keyframes modalSlideIn {
|
||
from {
|
||
transform: translateY(-50px) scale(0.9);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateY(0) scale(1);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
@keyframes fadeOut {
|
||
from { opacity: 1; }
|
||
to { opacity: 0; }
|
||
}
|
||
</style>
|
||
`;
|
||
|
||
document.body.appendChild(confirmOverlay);
|
||
|
||
// Close on backdrop click
|
||
confirmOverlay.querySelector('.confirmation-backdrop').onclick = cancelQuitTraining;
|
||
|
||
// ESC key handler
|
||
const escHandler = (e) => {
|
||
if (e.key === 'Escape') {
|
||
cancelQuitTraining();
|
||
}
|
||
};
|
||
document.addEventListener('keydown', escHandler);
|
||
confirmOverlay.escHandler = escHandler;
|
||
}
|
||
|
||
function getQuipyDialogContent() {
|
||
// Default content
|
||
let content = {
|
||
icon: '⚠️',
|
||
title: 'Quit Training Session?',
|
||
message: 'Are you sure you want to quit this training session?',
|
||
warningIcon: '🔥',
|
||
warning: 'Your progress will be saved, but you\'ll need to restart this session.',
|
||
quitIcon: '🚪',
|
||
quitText: 'Quit Training',
|
||
continueIcon: '💪',
|
||
continueText: 'Continue Training'
|
||
};
|
||
|
||
// Customize based on selected training mode
|
||
if (selectedTrainingMode) {
|
||
switch (selectedTrainingMode) {
|
||
case 'beginner':
|
||
content = {
|
||
icon: '🥺',
|
||
title: 'Giving Up Already?',
|
||
message: 'Come on, even beginners can handle more than this! You\'re just getting started.',
|
||
warningIcon: '💪',
|
||
warning: 'Real gooners don\'t quit during basic training. But hey, your choice...',
|
||
quitIcon: '😢',
|
||
quitText: 'I Give Up',
|
||
continueIcon: '🌟',
|
||
continueText: 'Keep Learning'
|
||
};
|
||
break;
|
||
|
||
case 'intermediate':
|
||
content = {
|
||
icon: '😏',
|
||
title: 'Can\'t Handle the Heat?',
|
||
message: 'Thought you were ready for intermediate training? Guess not everyone can level up.',
|
||
warningIcon: '🔥',
|
||
warning: 'You\'ll lose your momentum and have to rebuild that focus. Worth it?',
|
||
quitIcon: '🏃',
|
||
quitText: 'Retreat',
|
||
continueIcon: '🔥',
|
||
continueText: 'Bring the Heat'
|
||
};
|
||
break;
|
||
|
||
case 'advanced':
|
||
content = {
|
||
icon: '🤨',
|
||
title: 'Seriously? Advanced Gooner Quitting?',
|
||
message: 'An advanced gooner doesn\'t just walk away. This is embarrassing.',
|
||
warningIcon: '👑',
|
||
warning: 'You\'re supposed to be elite. Elite gooners finish what they start.',
|
||
quitIcon: '💸',
|
||
quitText: 'Be Weak',
|
||
continueIcon: '👑',
|
||
continueText: 'Stay Elite'
|
||
};
|
||
break;
|
||
|
||
case 'endurance':
|
||
content = {
|
||
icon: '💀',
|
||
title: 'Endurance Broken Already?',
|
||
message: 'The whole point is to build stamina. Quitting defeats the purpose entirely.',
|
||
warningIcon: '⏱️',
|
||
warning: 'Real endurance training means pushing through when it gets tough.',
|
||
quitIcon: '💔',
|
||
quitText: 'Tap Out',
|
||
continueIcon: '⚡',
|
||
continueText: 'Push Limits'
|
||
};
|
||
break;
|
||
|
||
case 'humiliation':
|
||
content = {
|
||
icon: '😈',
|
||
title: 'Too Humiliated to Continue?',
|
||
message: 'Aww, is the humiliation training too much for you? How... predictable.',
|
||
warningIcon: '🔥',
|
||
warning: 'Quitting now just proves you can\'t handle being properly trained.',
|
||
quitIcon: '🙈',
|
||
quitText: 'Run Away',
|
||
continueIcon: '😈',
|
||
continueText: 'Take More'
|
||
};
|
||
break;
|
||
|
||
case 'dress-up':
|
||
content = {
|
||
icon: '💄',
|
||
title: 'Not Feeling Pretty Enough?',
|
||
message: 'Can\'t handle getting all dressed up? But you were just starting to look so cute!',
|
||
warningIcon: '👗',
|
||
warning: 'You\'ll miss out on completing your fabulous transformation.',
|
||
quitIcon: '👔',
|
||
quitText: 'Stay Boring',
|
||
continueIcon: '💅',
|
||
continueText: 'Get Pretty'
|
||
};
|
||
break;
|
||
|
||
default:
|
||
// Keep default content for unknown modes
|
||
break;
|
||
}
|
||
}
|
||
|
||
// If we're in a scenario, add scenario-specific flavor
|
||
if (currentScenarioTask && currentScenarioTask.title) {
|
||
const scenario = currentScenarioTask.title.toLowerCase();
|
||
if (scenario.includes('sissy') || scenario.includes('feminiz')) {
|
||
content.icon = '💋';
|
||
content.title = 'Not Ready to Embrace It?';
|
||
} else if (scenario.includes('worship') || scenario.includes('goddess')) {
|
||
content.icon = '👸';
|
||
content.title = 'Abandoning Your Goddess?';
|
||
} else if (scenario.includes('edge') || scenario.includes('denial')) {
|
||
content.icon = '😤';
|
||
content.title = 'Can\'t Handle the Edge?';
|
||
}
|
||
}
|
||
|
||
return content;
|
||
}
|
||
|
||
function confirmQuitTraining() {
|
||
console.log('✅ Quit training confirmed');
|
||
|
||
// Remove confirmation overlay
|
||
const overlay = document.getElementById('quit-confirmation-overlay');
|
||
if (overlay) {
|
||
if (overlay.escHandler) {
|
||
document.removeEventListener('keydown', overlay.escHandler);
|
||
}
|
||
overlay.remove();
|
||
}
|
||
|
||
// Proceed with quitting
|
||
returnToModeSelection();
|
||
}
|
||
|
||
function cancelQuitTraining() {
|
||
console.log('❌ Quit training cancelled');
|
||
|
||
// Remove confirmation overlay
|
||
const overlay = document.getElementById('quit-confirmation-overlay');
|
||
if (overlay) {
|
||
if (overlay.escHandler) {
|
||
document.removeEventListener('keydown', overlay.escHandler);
|
||
}
|
||
overlay.style.animation = 'fadeOut 0.2s ease-out';
|
||
setTimeout(() => overlay.remove(), 200);
|
||
}
|
||
}
|
||
|
||
function returnToModeSelection() {
|
||
console.log('🔄 Returning to training mode selection...');
|
||
|
||
// Clear any active training state
|
||
selectedTrainingMode = null;
|
||
currentScenarioTask = null;
|
||
currentScenarioStep = 'start';
|
||
|
||
// Clear any game overrides and restore original state
|
||
if (window.game) {
|
||
// Stop the game session
|
||
window.game.gameState.isRunning = false;
|
||
window.game.gameState.isPaused = false;
|
||
|
||
// Save scenario XP to player stats before clearing
|
||
saveScenarioXPToPlayerStats();
|
||
|
||
// Hide status bar and stop XP tracking
|
||
hideScenarioStatusBar();
|
||
stopScenarioXPTracking();
|
||
|
||
// Clear current task
|
||
window.game.gameState.currentTask = null;
|
||
window.game.currentTask = null;
|
||
|
||
// Reset any overridden methods if needed
|
||
if (window.gameData && window.gameData.originalMainTasks) {
|
||
window.gameData.mainTasks = window.gameData.originalMainTasks;
|
||
}
|
||
}
|
||
|
||
// Show setup interface again
|
||
const academyHeader = document.querySelector('.academy-header');
|
||
if (academyHeader) {
|
||
// Reset header styling
|
||
academyHeader.style.padding = '';
|
||
academyHeader.style.background = '';
|
||
}
|
||
|
||
// Stop background video
|
||
const bgVideo = document.getElementById('backgroundVideo');
|
||
if (bgVideo) {
|
||
bgVideo.pause();
|
||
bgVideo.src = '';
|
||
}
|
||
|
||
// Show setup screen
|
||
const setupScreen = document.getElementById('academy-setup');
|
||
if (setupScreen) {
|
||
setupScreen.style.display = 'block';
|
||
}
|
||
|
||
// Show library status
|
||
const libraryStatus = document.querySelector('.library-status');
|
||
if (libraryStatus) {
|
||
libraryStatus.style.display = 'block';
|
||
}
|
||
|
||
// Hide all game-related containers
|
||
const gameInterface = document.getElementById('gameInterface');
|
||
if (gameInterface) {
|
||
gameInterface.style.display = 'none';
|
||
}
|
||
|
||
const gameContainer = document.getElementById('game-container');
|
||
if (gameContainer) {
|
||
gameContainer.style.display = 'none';
|
||
gameContainer.innerHTML = '';
|
||
}
|
||
|
||
const taskDisplay = document.getElementById('training-task-display');
|
||
if (taskDisplay) {
|
||
taskDisplay.style.display = 'none';
|
||
taskDisplay.innerHTML = '';
|
||
}
|
||
|
||
// Clear any overlays that might be covering the interface
|
||
const overlays = document.querySelectorAll('.camera-overlay, .verification-overlay, #camera-overlay');
|
||
overlays.forEach(overlay => {
|
||
if (overlay) overlay.remove();
|
||
});
|
||
|
||
// Reset training mode cards
|
||
document.querySelectorAll('.training-mode-card').forEach(card => {
|
||
card.classList.remove('selected');
|
||
});
|
||
|
||
// Disable floating start button until new mode is selected
|
||
const floatingStartBtn = document.getElementById('floating-academy-start-btn');
|
||
if (floatingStartBtn) {
|
||
floatingStartBtn.disabled = true;
|
||
}
|
||
|
||
// Restore video controls visibility
|
||
const videoControls = document.getElementById('videoControlsOverlay');
|
||
if (videoControls) {
|
||
videoControls.style.opacity = '1';
|
||
}
|
||
|
||
console.log('✅ Returned to training mode selection screen');
|
||
}
|
||
|
||
// Scenario Adventure Display System
|
||
|
||
function displayScenarioAdventure(container, task) {
|
||
console.log('🎭 Displaying scenario adventure:', task.id);
|
||
console.log('🎭 Task interactive data:', task.interactiveData);
|
||
console.log('🎭 Container element:', container);
|
||
|
||
currentScenarioTask = task;
|
||
currentScenarioStep = 'start';
|
||
|
||
if (!task.interactiveData || !task.interactiveData.steps) {
|
||
console.error('❌ No scenario data found for task:', task.id);
|
||
console.error('❌ Interactive data:', task.interactiveData);
|
||
return;
|
||
}
|
||
|
||
console.log('🎭 Starting scenario from step:', currentScenarioStep);
|
||
displayScenarioStep(container, currentScenarioStep);
|
||
}
|
||
|
||
function displayScenarioStep(container, stepId) {
|
||
if (!currentScenarioTask) {
|
||
console.error('❌ No current scenario task available');
|
||
return;
|
||
}
|
||
|
||
if (!currentScenarioTask.interactiveData) {
|
||
console.error('❌ No interactive data available for current scenario task');
|
||
return;
|
||
}
|
||
|
||
const scenario = currentScenarioTask.interactiveData;
|
||
const step = scenario.steps[stepId];
|
||
|
||
if (!step) {
|
||
console.error('❌ Scenario step not found:', stepId);
|
||
return;
|
||
}
|
||
|
||
console.log('🎭 Displaying scenario step:', stepId, step.type);
|
||
|
||
let stepHtml = '';
|
||
|
||
if (step.type === 'choice') {
|
||
stepHtml = `
|
||
<div class="training-task scenario-choice">
|
||
<h3>🎭 ${scenario.title}</h3>
|
||
<div class="scenario-story">
|
||
${step.story}
|
||
</div>
|
||
<div class="scenario-choices">
|
||
${step.choices.map((choice, index) => `
|
||
<div class="choice-option" onclick="selectScenarioChoice('${choice.nextStep}')">
|
||
<h4>${choice.text}</h4>
|
||
<p class="choice-preview">${choice.preview}</p>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
<div class="training-controls">
|
||
<button onclick="showQuitConfirmation()" class="next-btn">Quit Training</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else if (step.type === 'text') {
|
||
stepHtml = `
|
||
<div class="training-task scenario-text">
|
||
<h3>🎭 ${scenario.title}</h3>
|
||
<div class="scenario-story">
|
||
${step.story}
|
||
</div>
|
||
<div class="training-controls">
|
||
<button onclick="selectScenarioChoice('${step.nextStep}')" class="action-btn">Continue</button>
|
||
<button onclick="showQuitConfirmation()" class="next-btn">Quit Training</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else if (step.type === 'mirror-action') {
|
||
stepHtml = `
|
||
<div class="training-task scenario-mirror">
|
||
<h3>🪞 ${scenario.title}</h3>
|
||
<div class="scenario-story">
|
||
${step.story}
|
||
</div>
|
||
<div class="mirror-instructions">
|
||
<h4>📹 Mirror Instructions:</h4>
|
||
<p>${step.mirrorInstructions}</p>
|
||
</div>
|
||
<div class="mirror-task-content">
|
||
<h4>🎯 Task:</h4>
|
||
<p>${step.mirrorTaskText}</p>
|
||
<p><strong>Duration: ${step.duration} seconds</strong></p>
|
||
</div>
|
||
<div class="training-controls">
|
||
<button onclick="startMirrorAction('${step.nextStep}', ${step.duration})" class="action-btn">Start Mirror Task</button>
|
||
<button onclick="showQuitConfirmation()" class="skip-btn">Quit Training</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else if (step.type === 'action') {
|
||
const duration = step.duration || 60;
|
||
const minutes = Math.floor(duration / 60);
|
||
const seconds = duration % 60;
|
||
const timeDisplay = minutes > 0 ? `${minutes}:${seconds.toString().padStart(2, '0')}` : `${seconds}s`;
|
||
|
||
// Check if this is a photo-related action
|
||
const isPhotoAction = (step.actionText && step.actionText.toLowerCase().includes('photograph')) ||
|
||
(step.story && step.story.toLowerCase().includes('photograph')) ||
|
||
(step.story && step.story.toLowerCase().includes('photo')) ||
|
||
selectedTrainingMode === 'photography-studio';
|
||
|
||
stepHtml = `
|
||
<div class="training-task scenario-action">
|
||
<h3>🎭 ${scenario.title}</h3>
|
||
<div class="scenario-story">
|
||
${step.story}
|
||
</div>
|
||
<div class="action-details">
|
||
<h4>📋 Action Required:</h4>
|
||
<p class="action-text">${step.actionText}</p>
|
||
<p class="duration-text">⏱️ Duration: ${timeDisplay}</p>
|
||
</div>
|
||
${isPhotoAction ? `
|
||
<div class="photo-section" style="margin: 20px 0;">
|
||
<div class="webcam-controls">
|
||
<div class="photo-capture-controls" style="text-align: center; margin-top: 15px;">
|
||
<button id="start-photo-session-btn" onclick="startPhotoSession()" class="start-btn">
|
||
📸 Start Photo Session
|
||
</button>
|
||
<div id="photo-progress" style="display: none; margin-top: 10px;">
|
||
<p>📸 Photos taken: <span id="photos-count">0</span> / <span id="photos-needed">3</span></p>
|
||
<button onclick="completePhotoSession()" class="end-session-btn" style="margin-top: 10px; background: var(--color-warning); color: white; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer;">
|
||
🔚 End Session
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
<div class="action-timer" style="display: block;">
|
||
<p>⏱️ Time remaining: <span id="timer-display">${Math.floor(duration/60)}:${(duration%60).toString().padStart(2,'0')}</span></p>
|
||
<div class="progress-bar">
|
||
<div id="progress-fill" class="progress-fill"></div>
|
||
</div>
|
||
<p><strong>⚠️ You must complete the full timer to progress.</strong></p>
|
||
</div>
|
||
<div class="training-controls">
|
||
<button id="complete-action-btn" onclick="completeScenarioAction('${step.nextStep}')" class="complete-btn" disabled>
|
||
Action Complete (<span id="btn-timer">${Math.floor(duration/60)}:${(duration%60).toString().padStart(2,'0')}</span>)
|
||
</button>
|
||
<button onclick="showQuitConfirmation()" class="next-btn">Quit Training</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Auto-start the timer immediately when displaying this step
|
||
setTimeout(() => {
|
||
startActionTimer(duration, step.nextStep, true);
|
||
}, 100); // Small delay to ensure DOM is ready
|
||
} else if (step.type === 'completion') {
|
||
stepHtml = `
|
||
<div class="training-task scenario-completion">
|
||
<h3>✅ ${scenario.title} - Complete!</h3>
|
||
<div class="scenario-story">
|
||
${step.story}
|
||
</div>
|
||
<div class="completion-outcome">
|
||
<h4>🎯 Outcome: ${step.outcome || 'Completed'}</h4>
|
||
</div>
|
||
<div class="training-controls">
|
||
<button onclick="loadNextTrainingTask()" class="complete-btn">Continue Training</button>
|
||
<button onclick="location.reload()" class="next-btn">New Session</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else if (step.type === 'verification-required') {
|
||
stepHtml = `
|
||
<div class="training-task scenario-verification">
|
||
<h3>🔍 ${scenario.title} - Verification Required</h3>
|
||
<div class="scenario-story">
|
||
${step.story}
|
||
</div>
|
||
<div class="verification-requirements">
|
||
<h4>📋 Position Requirements:</h4>
|
||
<p class="verification-instructions">${step.verificationInstructions}</p>
|
||
<p class="verification-text">${step.verificationText}</p>
|
||
<p class="verification-duration"><strong>Duration:</strong> ${step.verificationDuration} seconds</p>
|
||
</div>
|
||
<div class="verification-warning">
|
||
<p>⚠️ <strong>Warning:</strong> You must maintain the required position for the full duration. The webcam will verify your compliance.</p>
|
||
</div>
|
||
<div class="training-controls">
|
||
<button onclick="startPositionVerification('${step.nextStep}', ${step.verificationDuration}, '${step.verificationInstructions}', '${step.verificationText}')" class="action-btn">🔍 Start Position Verification</button>
|
||
<button onclick="showQuitConfirmation()" class="skip-btn">Quit Training</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else if (step.type === 'ending') {
|
||
stepHtml = `
|
||
<div class="training-task scenario-ending">
|
||
<h3>🎉 ${scenario.title} - Complete!</h3>
|
||
<div class="scenario-story">
|
||
${step.story}
|
||
</div>
|
||
<div class="ending-details">
|
||
<h4>🏆 ${step.endingText}</h4>
|
||
<p class="outcome-text">Outcome: ${step.outcome}</p>
|
||
</div>
|
||
<div class="training-controls">
|
||
<button onclick="completeTrainingTask()" class="complete-btn">Complete Training</button>
|
||
<button onclick="showQuitConfirmation()" class="next-btn">Quit Training</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
container.innerHTML = stepHtml;
|
||
|
||
// TTS narration for scenario steps
|
||
setTimeout(() => {
|
||
if (step.type === 'choice') {
|
||
speakText(`${scenario.title}. ${step.story}. Choose your path carefully.`);
|
||
} else if (step.type === 'text') {
|
||
speakText(`${scenario.title}. ${step.story}`);
|
||
} else if (step.type === 'action') {
|
||
const minutes = Math.floor((step.duration || 60) / 60);
|
||
const seconds = (step.duration || 60) % 60;
|
||
let durationText = minutes > 0 ? `${minutes} minutes` : `${seconds} seconds`;
|
||
if (minutes > 0 && seconds > 0) durationText = `${minutes} minutes and ${seconds} seconds`;
|
||
|
||
speakText(`${scenario.title}. ${step.story}. Action required: ${step.actionText}. Duration: ${durationText}. Begin when ready.`);
|
||
} else if (step.type === 'mirror-action') {
|
||
speakText(`${scenario.title}. ${step.story}. Mirror task: ${step.mirrorTaskText}. Duration: ${step.duration} seconds.`);
|
||
} else if (step.type === 'verification-required') {
|
||
speakText(`${scenario.title}. Position verification required. ${step.story}. ${step.verificationInstructions}.`);
|
||
} else if (step.type === 'completion' || step.type === 'ending') {
|
||
speakText(`${scenario.title} complete! ${step.story}. Well done.`);
|
||
}
|
||
}, 800);
|
||
}
|
||
|
||
function selectScenarioChoice(nextStep) {
|
||
console.log('🎯 Selected scenario choice, moving to:', nextStep);
|
||
currentScenarioStep = nextStep;
|
||
|
||
// Find the appropriate container
|
||
const gameContainer = document.getElementById('game-container');
|
||
const fallbackContainer = document.getElementById('training-task-display');
|
||
const container = gameContainer || fallbackContainer;
|
||
|
||
if (container) {
|
||
displayScenarioStep(container, nextStep);
|
||
} else {
|
||
console.error('❌ No container found for scenario display');
|
||
}
|
||
}
|
||
|
||
function startMirrorAction(nextStep, duration) {
|
||
console.log('🪞 Starting mirror action for', duration, 'seconds');
|
||
|
||
// Store next step for completion callback
|
||
window.trainingAcademyNextStep = nextStep;
|
||
|
||
// Always start the visual timer regardless of webcam status
|
||
startActionTimer(duration, nextStep, true);
|
||
|
||
// Create mirror task data for the webcam system
|
||
const mirrorTaskData = {
|
||
mirrorInstructions: currentScenarioTask.interactiveData.steps[currentScenarioStep].mirrorInstructions,
|
||
mirrorTaskText: currentScenarioTask.interactiveData.steps[currentScenarioStep].mirrorTaskText,
|
||
duration: duration,
|
||
taskText: currentScenarioTask.interactiveData.steps[currentScenarioStep].mirrorTaskText,
|
||
onComplete: function() {
|
||
console.log('🪞 Mirror task completed by user, proceeding to:', window.trainingAcademyNextStep);
|
||
proceedToNextStep(window.trainingAcademyNextStep);
|
||
}
|
||
};
|
||
|
||
// Use the webcam manager to start mirror mode
|
||
if (window.game && window.game.webcamManager) {
|
||
console.log('🎥 Starting webcam mirror mode...');
|
||
window.game.webcamManager.startMirrorMode(mirrorTaskData).then((success) => {
|
||
if (success) {
|
||
console.log('🪞 Mirror mode started successfully');
|
||
} else {
|
||
console.warn('⚠️ Mirror mode failed to start, timer will handle completion');
|
||
}
|
||
}).catch(error => {
|
||
console.error('❌ Mirror mode failed:', error);
|
||
console.log('⚠️ Timer will handle completion');
|
||
});
|
||
} else {
|
||
console.warn('⚠️ Webcam manager not available, using timer only');
|
||
}
|
||
}
|
||
|
||
function proceedToNextStep(nextStep) {
|
||
// Prevent duplicate calls
|
||
if (window.trainingAcademyProcessing) {
|
||
console.log('🚫 Already processing next step, ignoring duplicate call');
|
||
return;
|
||
}
|
||
window.trainingAcademyProcessing = true;
|
||
|
||
setTimeout(() => {
|
||
currentScenarioStep = nextStep;
|
||
|
||
// Find the appropriate container and continue scenario
|
||
const gameContainer = document.getElementById('game-container');
|
||
const fallbackContainer = document.getElementById('training-task-display');
|
||
const container = gameContainer || fallbackContainer;
|
||
|
||
if (container) {
|
||
displayScenarioStep(container, nextStep);
|
||
}
|
||
|
||
window.trainingAcademyProcessing = false;
|
||
}, 100);
|
||
}
|
||
|
||
function startPositionVerification(nextStep, duration, instructions, verificationText) {
|
||
console.log('🔍 Starting position verification for', duration, 'seconds');
|
||
|
||
// Store next step for completion callback
|
||
window.trainingAcademyNextStep = nextStep;
|
||
|
||
// Emergency timeout: force close if verification takes too long (5 minutes)
|
||
const emergencyTimeout = setTimeout(() => {
|
||
console.warn('🚨 Emergency timeout: Force closing stuck verification');
|
||
const overlay = document.getElementById('verification-overlay');
|
||
if (overlay) {
|
||
overlay.remove();
|
||
}
|
||
// Clear any verification timers
|
||
if (window.game?.webcamManager) {
|
||
window.game.webcamManager.closeVerificationMode();
|
||
}
|
||
alert('Verification timed out. Skipping to next step.');
|
||
proceedToNextStep(nextStep);
|
||
}, 5 * 60 * 1000); // 5 minutes
|
||
|
||
// Store timeout reference for cleanup
|
||
window.verificationEmergencyTimeout = emergencyTimeout;
|
||
|
||
// Create verification data for the webcam system
|
||
const verificationData = {
|
||
instructions: "Position verification required for training compliance",
|
||
verificationInstructions: instructions,
|
||
verificationText: verificationText,
|
||
verificationDuration: duration
|
||
// Note: Removed onComplete callback to avoid conflicts with event system
|
||
};
|
||
|
||
// Use the webcam manager to start verification mode
|
||
if (window.game && window.game.webcamManager) {
|
||
console.log('🎥 Starting webcam verification mode...');
|
||
window.game.webcamManager.startVerificationMode(verificationData).then((success) => {
|
||
if (success) {
|
||
console.log('🔍 Verification mode started successfully');
|
||
|
||
// Listen for verification completion
|
||
document.addEventListener('verificationComplete', function(event) {
|
||
console.log('📨 Verification complete event received:', event.detail);
|
||
|
||
// Clear emergency timeout
|
||
if (window.verificationEmergencyTimeout) {
|
||
clearTimeout(window.verificationEmergencyTimeout);
|
||
window.verificationEmergencyTimeout = null;
|
||
console.log('⏰ Cleared emergency timeout');
|
||
}
|
||
|
||
if (event.detail.success) {
|
||
console.log('✅ Verification completed successfully, proceeding to:', nextStep);
|
||
proceedToNextStep(nextStep);
|
||
} else {
|
||
console.warn('⚠️ Verification failed or was abandoned');
|
||
}
|
||
}, { once: true });
|
||
|
||
} else {
|
||
console.warn('⚠️ Verification mode failed to start');
|
||
// Show failure and allow skip or retry
|
||
alert('Camera access required for position verification. Please enable camera access.');
|
||
}
|
||
}).catch(error => {
|
||
console.error('❌ Verification mode failed:', error);
|
||
alert('Failed to start position verification. Check camera access.');
|
||
});
|
||
} else {
|
||
console.warn('⚠️ Webcam manager not available');
|
||
alert('Webcam verification system not available. Cannot proceed with position verification.');
|
||
}
|
||
}
|
||
|
||
function startScenarioAction(nextStep, duration) {
|
||
console.log('🎬 Starting scenario action for', duration, 'seconds');
|
||
|
||
// Check if current task is focus-hold type - use interactive system
|
||
if (currentScenarioTask && currentScenarioTask.interactiveType === 'focus-hold') {
|
||
console.log('🧘 Detected focus-hold task - using interactive focus system');
|
||
startFocusHoldAction(nextStep, duration);
|
||
return;
|
||
}
|
||
|
||
// For regular timed actions, the timer should already be running
|
||
// This function is now mainly a fallback if timer wasn't auto-started
|
||
console.log('⏰ Timer should already be running for this action');
|
||
|
||
// Start timer if it's not already running
|
||
if (!document.getElementById('timer-display') || document.getElementById('timer-display').textContent.includes(':')) {
|
||
startActionTimer(duration, nextStep, true);
|
||
}
|
||
}
|
||
|
||
function startFocusHoldAction(nextStep, duration) {
|
||
console.log('🧘 Starting focus-hold action for', duration, 'seconds');
|
||
|
||
// Set up focus mode
|
||
if (window.game && window.game.gameState) {
|
||
window.game.gameState.isInFocusActivity = true;
|
||
}
|
||
|
||
// Find the appropriate container
|
||
const gameContainer = document.getElementById('game-container');
|
||
const fallbackContainer = document.getElementById('training-task-display');
|
||
const container = gameContainer || fallbackContainer;
|
||
|
||
if (container && window.game && window.game.interactiveTaskManager) {
|
||
// Create a focus task object
|
||
const focusTask = {
|
||
...currentScenarioTask,
|
||
duration: duration,
|
||
instructions: currentScenarioTask.story || 'Focus and follow the instructions'
|
||
};
|
||
|
||
// Use the game's interactive task manager to create the focus task
|
||
window.game.interactiveTaskManager.createFocusTask(focusTask, container).then(() => {
|
||
console.log('🧘 Focus task created successfully');
|
||
|
||
// Override the completion to advance the scenario
|
||
overrideFocusTaskCompletion(nextStep);
|
||
});
|
||
} else {
|
||
console.error('❌ Focus system not available, falling back to standard timer');
|
||
// Fallback to standard timer
|
||
startStandardTimer(nextStep, duration);
|
||
}
|
||
}
|
||
|
||
function overrideFocusTaskCompletion(nextStep) {
|
||
// Wait for focus task to initialize then override completion
|
||
setTimeout(() => {
|
||
const completeBtn = document.querySelector('#complete-task, .complete-btn');
|
||
const skipBtn = document.querySelector('#skip-task, .next-btn');
|
||
|
||
// Hide original buttons if they exist
|
||
if (completeBtn) completeBtn.style.display = 'none';
|
||
if (skipBtn) skipBtn.style.display = 'none';
|
||
|
||
// Add scenario advancement when focus completes
|
||
const originalCompleteHandler = window.game?.interactiveTaskManager?.completeCurrentTask;
|
||
if (originalCompleteHandler) {
|
||
window.game.interactiveTaskManager.completeCurrentTask = function() {
|
||
console.log('🧘 Focus task completed - advancing scenario');
|
||
|
||
// End focus mode
|
||
if (window.game && window.game.gameState) {
|
||
window.game.gameState.isInFocusActivity = false;
|
||
}
|
||
|
||
// Advance to next scenario step
|
||
setTimeout(() => {
|
||
completeScenarioAction(nextStep);
|
||
}, 1000);
|
||
|
||
return originalCompleteHandler.call(this);
|
||
};
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
function startStandardTimer(nextStep, duration) {
|
||
// Fallback standard timer implementation
|
||
const container = document.getElementById('game-container') || document.getElementById('training-task-display');
|
||
|
||
if (container) {
|
||
container.innerHTML = `
|
||
<div class="training-task scenario-action-progress">
|
||
<h3>🎭 Focus Training in Progress</h3>
|
||
<div class="action-timer">
|
||
<p>⏱️ Time remaining: <span id="timer-display">${Math.floor(duration/60)}:${(duration%60).toString().padStart(2,'0')}</span></p>
|
||
<div class="progress-bar">
|
||
<div id="progress-fill" class="progress-fill"></div>
|
||
</div>
|
||
</div>
|
||
<div class="action-status">
|
||
<p>🧘 <strong>Focus on the training and maintain your position...</strong></p>
|
||
<p><strong>⚠️ Complete the full duration to progress.</strong></p>
|
||
</div>
|
||
<div class="training-controls">
|
||
<button id="complete-action-btn" onclick="completeScenarioAction('${nextStep}')" class="complete-btn" disabled>
|
||
Focus Complete (<span id="btn-timer">${Math.floor(duration/60)}:${(duration%60).toString().padStart(2,'0')}</span>)
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
startActionTimer(duration, nextStep, true);
|
||
}
|
||
}
|
||
|
||
function startActionTimer(duration, nextStep, forceComplete = false) {
|
||
let timeLeft = duration;
|
||
const timerDisplay = document.getElementById('timer-display');
|
||
const progressFill = document.getElementById('progress-fill');
|
||
const completeBtn = document.getElementById('complete-action-btn');
|
||
const btnTimer = document.getElementById('btn-timer');
|
||
|
||
console.log('⏱️ Starting action timer:', duration, 'seconds, forced:', forceComplete);
|
||
|
||
const timer = setInterval(() => {
|
||
timeLeft--;
|
||
|
||
if (timerDisplay) {
|
||
const minutes = Math.floor(timeLeft / 60);
|
||
const seconds = timeLeft % 60;
|
||
timerDisplay.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||
}
|
||
|
||
if (btnTimer) {
|
||
const minutes = Math.floor(timeLeft / 60);
|
||
const seconds = timeLeft % 60;
|
||
btnTimer.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||
}
|
||
|
||
if (progressFill) {
|
||
const progress = ((duration - timeLeft) / duration) * 100;
|
||
progressFill.style.width = progress + '%';
|
||
}
|
||
|
||
if (timeLeft <= 0) {
|
||
clearInterval(timer);
|
||
|
||
if (forceComplete) {
|
||
// Enable the complete button and auto-advance
|
||
if (completeBtn) {
|
||
completeBtn.disabled = false;
|
||
completeBtn.textContent = 'Action Complete - Click to Continue';
|
||
completeBtn.style.background = 'linear-gradient(45deg, #28a745, #20c997)';
|
||
}
|
||
|
||
if (timerDisplay) {
|
||
timerDisplay.textContent = 'COMPLETE!';
|
||
timerDisplay.style.color = '#28a745';
|
||
}
|
||
|
||
// Auto-advance after 2 seconds
|
||
setTimeout(() => {
|
||
completeScenarioAction(nextStep);
|
||
}, 2000);
|
||
} else {
|
||
completeScenarioAction(nextStep);
|
||
}
|
||
}
|
||
}, 1000);
|
||
|
||
// Store timer reference for potential cleanup
|
||
window.currentActionTimer = timer;
|
||
}
|
||
|
||
function completeScenarioAction(nextStep) {
|
||
console.log('✅ Scenario action completed, moving to:', nextStep);
|
||
selectScenarioChoice(nextStep);
|
||
}
|
||
|
||
function displayFocusHoldTask(container, task) {
|
||
console.log('🧘 Displaying focus-hold task:', task.id);
|
||
|
||
// Set up focus mode
|
||
if (window.game && window.game.gameState) {
|
||
window.game.gameState.isInFocusActivity = true;
|
||
}
|
||
|
||
if (window.game && window.game.interactiveTaskManager) {
|
||
// Use the interactive task manager to create the focus task
|
||
window.game.interactiveTaskManager.createFocusTask(task, container).then(() => {
|
||
console.log('🧘 Focus task created successfully');
|
||
|
||
// Override completion to advance to next training task
|
||
overrideFocusTaskForTraining();
|
||
});
|
||
} else {
|
||
console.log('⚠️ Interactive task manager not available, showing simple focus display');
|
||
|
||
// Fallback simple focus display
|
||
container.innerHTML = `
|
||
<div class="training-task focus-task-simple">
|
||
<h3>🧘 ${task.text}</h3>
|
||
<p><strong>Difficulty:</strong> ${task.difficulty}</p>
|
||
<p><strong>Type:</strong> Focus Training</p>
|
||
<div class="task-content">
|
||
${task.text}
|
||
</div>
|
||
${task.story ? `
|
||
<div class="task-story">
|
||
<p><strong>Instructions:</strong> ${task.story}</p>
|
||
</div>
|
||
` : ''}
|
||
<div class="focus-instructions">
|
||
<p>🧘 <strong>Focus training requires your complete attention.</strong></p>
|
||
<p>Duration: ${task.duration || 60} seconds</p>
|
||
</div>
|
||
<div class="training-controls">
|
||
<button onclick="startSimpleFocusSession(${task.duration || 60})" class="complete-btn">Start Focus Session</button>
|
||
<button onclick="showQuitConfirmation()" class="next-btn">Quit Training</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
function overrideFocusTaskForTraining() {
|
||
// Override the interactive task completion to advance training
|
||
setTimeout(() => {
|
||
const originalCompleteHandler = window.game?.interactiveTaskManager?.completeCurrentTask;
|
||
if (originalCompleteHandler) {
|
||
window.game.interactiveTaskManager.completeCurrentTask = function() {
|
||
console.log('🧘 Focus task completed - advancing to next training task');
|
||
|
||
// End focus mode
|
||
if (window.game && window.game.gameState) {
|
||
window.game.gameState.isInFocusActivity = false;
|
||
}
|
||
|
||
// Advance to next training task
|
||
setTimeout(() => {
|
||
loadNextTrainingTask();
|
||
}, 1000);
|
||
|
||
return originalCompleteHandler.call(this);
|
||
};
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
function startSimpleFocusSession(duration) {
|
||
console.log('🧘 Starting simple focus session for', duration, 'seconds');
|
||
|
||
const container = document.getElementById('game-container') || document.getElementById('training-task-display');
|
||
|
||
if (container) {
|
||
container.innerHTML = `
|
||
<div class="training-task focus-session-active">
|
||
<h3>🧘 Focus Session Active</h3>
|
||
<div class="focus-timer-large">
|
||
<span id="focus-timer-display">${Math.floor(duration/60)}:${(duration%60).toString().padStart(2,'0')}</span>
|
||
</div>
|
||
<div class="focus-instructions">
|
||
<p>🎯 <strong>Maintain your focus and position</strong></p>
|
||
<p>📱 Do not look away from the screen</p>
|
||
<p>🧘 Breathe steadily and concentrate</p>
|
||
</div>
|
||
<div class="progress-bar">
|
||
<div id="focus-progress-fill" class="progress-fill"></div>
|
||
</div>
|
||
<div class="focus-status">
|
||
<p id="focus-status-text">Focus session in progress...</p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// TTS guidance for focus session
|
||
speakText(`Beginning focus session. ${duration} seconds of concentrated attention required. Maintain your position, breathe steadily, and do not look away from the screen.`);
|
||
|
||
startFocusTimer(duration);
|
||
}
|
||
}
|
||
|
||
function startFocusTimer(duration) {
|
||
let timeLeft = duration;
|
||
const timerDisplay = document.getElementById('focus-timer-display');
|
||
const progressFill = document.getElementById('focus-progress-fill');
|
||
const statusText = document.getElementById('focus-status-text');
|
||
|
||
const timer = setInterval(() => {
|
||
timeLeft--;
|
||
|
||
if (timerDisplay) {
|
||
const minutes = Math.floor(timeLeft / 60);
|
||
const seconds = timeLeft % 60;
|
||
timerDisplay.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||
}
|
||
|
||
if (progressFill) {
|
||
const progress = ((duration - timeLeft) / duration) * 100;
|
||
progressFill.style.width = progress + '%';
|
||
}
|
||
|
||
if (statusText) {
|
||
if (timeLeft > 30) {
|
||
statusText.textContent = 'Focus session in progress... maintain concentration';
|
||
} else if (timeLeft > 10) {
|
||
statusText.textContent = 'Almost complete... keep focusing';
|
||
statusText.style.color = '#ff9800';
|
||
// TTS guidance at halfway point (only once)
|
||
if (timeLeft === 30 || (timeLeft > 25 && timeLeft <= 30)) {
|
||
queueTTS('Halfway complete. Maintain your focus and position.');
|
||
}
|
||
} else {
|
||
statusText.textContent = 'Final moments... maintain focus!';
|
||
statusText.style.color = '#f44336';
|
||
// TTS final countdown (only once)
|
||
if (timeLeft === 10) {
|
||
queueTTS('Final ten seconds. Stay focused.');
|
||
} else if (timeLeft === 5) {
|
||
queueTTS('Five seconds remaining. Almost complete.');
|
||
}
|
||
}
|
||
}
|
||
|
||
if (timeLeft <= 0) {
|
||
clearInterval(timer);
|
||
completeFocusSession();
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
function completeFocusSession() {
|
||
console.log('🧘 Focus session completed successfully');
|
||
|
||
// TTS completion announcement
|
||
speakText('Focus session complete! Excellent concentration and discipline. You have successfully completed this training module.');
|
||
|
||
// End focus mode
|
||
if (window.game && window.game.gameState) {
|
||
window.game.gameState.isInFocusActivity = false;
|
||
}
|
||
|
||
const container = document.getElementById('game-container') || document.getElementById('training-task-display');
|
||
|
||
if (container) {
|
||
container.innerHTML = `
|
||
<div class="training-task focus-completed">
|
||
<h3>🎉 Focus Session Complete!</h3>
|
||
<div class="completion-message">
|
||
<p>✅ <strong>Excellent focus and concentration!</strong></p>
|
||
<p>You have successfully completed the focus training.</p>
|
||
</div>
|
||
<div class="training-controls">
|
||
<button onclick="loadNextTrainingTask()" class="complete-btn">Continue Training</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// Initialize Training Academy
|
||
async function initializeTrainingAcademy() {
|
||
console.log('🎓 Initializing Gooner Training Academy...');
|
||
|
||
try {
|
||
// Initialize TTS system
|
||
await initializeTTS();
|
||
|
||
// Initialize PlayerStats first
|
||
if (typeof PlayerStats !== 'undefined' && !window.playerStats) {
|
||
window.playerStats = new PlayerStats();
|
||
console.log('📊 PlayerStats initialized in Training Academy');
|
||
} else if (window.playerStats) {
|
||
console.log('📊 PlayerStats already available');
|
||
} else {
|
||
console.warn('⚠️ PlayerStats class not available');
|
||
}
|
||
|
||
// Set up training mode selection
|
||
setupTrainingModeSelection();
|
||
|
||
// Initialize setup screen
|
||
initializeAcademySetupScreen();
|
||
|
||
// Initialize libraries
|
||
await initializeVideoLibrary();
|
||
await initializePhotoLibrary();
|
||
|
||
console.log('✅ Training Academy ready');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error initializing Training Academy:', error);
|
||
}
|
||
}
|
||
|
||
// ===== ANIMATION EVENT LISTENERS =====
|
||
|
||
// Listen for level up events
|
||
window.addEventListener('levelUp', (event) => {
|
||
console.log('🎉 Training Academy: Level up event received:', event.detail);
|
||
});
|
||
|
||
// Listen for achievement events
|
||
window.addEventListener('achievementUnlocked', (event) => {
|
||
console.log('🏆 Training Academy: Achievement unlocked event received:', event.detail);
|
||
});
|
||
|
||
// Start initialization when DOM is loaded
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
console.log('🎓 Training Academy DOM loaded');
|
||
|
||
// Initialize desktop file manager if in Electron environment
|
||
if (window.electronAPI && typeof DesktopFileManager !== 'undefined') {
|
||
// Create a simple data manager for the file manager
|
||
const simpleDataManager = {
|
||
get: (key) => {
|
||
try {
|
||
return JSON.parse(localStorage.getItem(key));
|
||
} catch {
|
||
return null;
|
||
}
|
||
},
|
||
set: (key, value) => {
|
||
localStorage.setItem(key, JSON.stringify(value));
|
||
}
|
||
};
|
||
|
||
window.desktopFileManager = new DesktopFileManager(simpleDataManager);
|
||
console.log('🖥️ Desktop File Manager initialized for training academy');
|
||
|
||
// Track saved photo IDs to prevent duplicates
|
||
const savedPhotoIds = new Set();
|
||
|
||
// Intercept localStorage.setItem to save photos to file system
|
||
const originalSetItem = localStorage.setItem.bind(localStorage);
|
||
localStorage.setItem = function(key, value) {
|
||
// Call original first
|
||
originalSetItem(key, value);
|
||
|
||
// If it's a photo being saved, also save to file system
|
||
if ((key === 'capturedPhotos' || key === 'verificationPhotos') && window.desktopFileManager?.isElectron) {
|
||
try {
|
||
const photos = JSON.parse(value);
|
||
if (Array.isArray(photos) && photos.length > 0) {
|
||
const lastPhoto = photos[photos.length - 1];
|
||
if (lastPhoto.data) {
|
||
// Create unique ID based on timestamp and first 20 chars of data
|
||
const photoId = `${lastPhoto.timestamp}_${lastPhoto.data.substring(0, 20)}`;
|
||
|
||
// Only save if we haven't saved this photo yet
|
||
if (!savedPhotoIds.has(photoId)) {
|
||
savedPhotoIds.add(photoId);
|
||
|
||
// Save to file system asynchronously
|
||
window.desktopFileManager.savePhoto(lastPhoto.data, 'training-academy')
|
||
.then(photoData => {
|
||
if (photoData) {
|
||
console.log('📸 Intercepted and saved photo to file system:', photoData.filename);
|
||
}
|
||
})
|
||
.catch(err => console.error('📸 Failed to intercept save:', err));
|
||
}
|
||
}
|
||
}
|
||
} catch (err) {
|
||
// Ignore parsing errors
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
// Initialize theme switcher UI
|
||
if (window.themeManager) {
|
||
const themeSwitcher = window.themeManager.createThemeToggle();
|
||
const container = document.getElementById('theme-switcher-container');
|
||
if (container) {
|
||
container.appendChild(themeSwitcher);
|
||
console.log('✅ Theme switcher initialized');
|
||
}
|
||
}
|
||
|
||
// Wait a moment for all scripts to load
|
||
setTimeout(initializeTrainingAcademy, 1000);
|
||
});
|
||
|
||
// Listen for WebcamManager photo session completion
|
||
document.addEventListener('photoSessionComplete', (event) => {
|
||
console.log('📸 Photo session completed by WebcamManager:', event.detail);
|
||
|
||
// Handle completion for training academy photo sessions
|
||
if (photoSessionActive && event.detail && event.detail.photos) {
|
||
const photosCount = event.detail.photos.length;
|
||
console.log(`📸 Training photo session complete: ${photosCount} photos taken`);
|
||
|
||
// Update captured photos count
|
||
photosCaptured = photosCount;
|
||
|
||
// Process the completion
|
||
handlePhotoSessionCompletion();
|
||
|
||
// Reset session state
|
||
photoSessionActive = false;
|
||
}
|
||
});
|
||
|
||
// Listen for game ready events
|
||
window.addEventListener('gameReady', (event) => {
|
||
console.log('🎮 Game engine ready for training');
|
||
});
|
||
|
||
// Photo Session Functions for Action Steps
|
||
let photoSessionActive = false;
|
||
let photosCaptured = 0;
|
||
let photosNeeded = 3;
|
||
let currentWebcamManager = null;
|
||
|
||
async function startPhotoSession() {
|
||
console.log('📸 Starting photo session for action step');
|
||
|
||
try {
|
||
photoSessionActive = true;
|
||
photosCaptured = 0;
|
||
|
||
// Initialize webcam manager if not already done
|
||
if (!currentWebcamManager) {
|
||
currentWebcamManager = new WebcamManager(window.game);
|
||
await currentWebcamManager.init();
|
||
console.log('📸 WebcamManager initialized for photo session');
|
||
}
|
||
|
||
// Update UI elements
|
||
document.getElementById('start-photo-session-btn').style.display = 'none';
|
||
document.getElementById('photo-progress').style.display = 'block';
|
||
updatePhotoProgress();
|
||
|
||
// Start photo session
|
||
const sessionData = {
|
||
requirements: {
|
||
count: photosNeeded,
|
||
description: 'Take photos during this feminization session'
|
||
}
|
||
};
|
||
|
||
const success = await currentWebcamManager.startPhotoSession('dress-up-session', sessionData);
|
||
|
||
if (!success) {
|
||
throw new Error('Failed to start webcam session');
|
||
}
|
||
|
||
// Monitor photo progress for display only (completion handled by WebcamManager)
|
||
const checkPhotoProgress = setInterval(() => {
|
||
// Only update display, don't trigger completion
|
||
const sessionPhotos = currentWebcamManager.currentPhotoSession ?
|
||
currentWebcamManager.currentPhotoSession.photos.length : 0;
|
||
const newPhotoCount = sessionPhotos;
|
||
if (newPhotoCount !== photosCaptured) {
|
||
photosCaptured = newPhotoCount;
|
||
updatePhotoProgress();
|
||
|
||
// Don't auto-complete here - let WebcamManager handle it
|
||
// The photoSessionComplete event listener will handle completion
|
||
}
|
||
}, 500); // Check every 500ms for display updates only
|
||
|
||
// Store the interval so we can clear it if needed
|
||
window.photoProgressInterval = checkPhotoProgress;
|
||
|
||
console.log('📸 Photo session started successfully');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Failed to start photo session:', error);
|
||
alert('Failed to start camera. Please ensure camera access is allowed.');
|
||
photoSessionActive = false;
|
||
|
||
// Clear any intervals that might have been set
|
||
if (window.photoProgressInterval) {
|
||
clearInterval(window.photoProgressInterval);
|
||
window.photoProgressInterval = null;
|
||
}
|
||
|
||
// Reset UI
|
||
document.getElementById('start-photo-session-btn').style.display = 'block';
|
||
document.getElementById('photo-progress').style.display = 'none';
|
||
}
|
||
}
|
||
|
||
function capturePhotoForSession() {
|
||
if (!photoSessionActive || !currentWebcamManager) {
|
||
return;
|
||
}
|
||
|
||
console.log('📸 Capturing photo for session');
|
||
|
||
// Increment photo count
|
||
photosCaptured++;
|
||
updatePhotoProgress();
|
||
|
||
// Check if we've taken enough photos
|
||
if (photosCaptured >= photosNeeded) {
|
||
setTimeout(completePhotoSession, 1000); // Small delay to show the final photo
|
||
}
|
||
}
|
||
|
||
function updatePhotoProgress() {
|
||
const photosCountEl = document.getElementById('photos-count');
|
||
const photosNeededEl = document.getElementById('photos-needed');
|
||
|
||
if (photosCountEl) photosCountEl.textContent = photosCaptured;
|
||
if (photosNeededEl) photosNeededEl.textContent = photosNeeded;
|
||
}
|
||
|
||
async function completePhotoSession() {
|
||
// Prevent multiple calls
|
||
if (!photoSessionActive) {
|
||
console.log('📸 Photo session already completed, skipping');
|
||
return;
|
||
}
|
||
|
||
console.log('📸 Photo session completed');
|
||
photoSessionActive = false;
|
||
|
||
// Clear the photo progress monitoring interval
|
||
if (window.photoProgressInterval) {
|
||
clearInterval(window.photoProgressInterval);
|
||
window.photoProgressInterval = null;
|
||
}
|
||
|
||
// Get newly captured photos from WebcamManager's current session
|
||
const capturedPhotos = currentWebcamManager && currentWebcamManager.currentPhotoSession ?
|
||
currentWebcamManager.currentPhotoSession.photos : [];
|
||
console.log(`📸 Found ${capturedPhotos.length} captured photos`);
|
||
|
||
// Add captured photos to training photo library
|
||
if (capturedPhotos.length > 0) {
|
||
await addCapturedPhotosToLibrary(capturedPhotos);
|
||
}
|
||
|
||
// Show completion message
|
||
const progressDiv = document.getElementById('photo-progress');
|
||
if (progressDiv) {
|
||
progressDiv.innerHTML = `
|
||
<div style="color: var(--color-success); font-weight: bold; margin-top: 10px;">
|
||
✅ Photo session complete! ${photosCaptured} photos captured and added to gallery.
|
||
</div>
|
||
<button onclick="showCapturedPhotos()" style="margin-top: 10px; padding: 8px 16px; background: var(--color-primary); color: white; border: none; border-radius: 5px; cursor: pointer;">
|
||
📖 View Captured Photos
|
||
</button>
|
||
`;
|
||
}
|
||
|
||
// WebcamManager has already ended the session when firing photoSessionComplete event
|
||
// Just ensure we clean up our training academy state
|
||
console.log('📸 Photo session completion handled by training academy');
|
||
}
|
||
|
||
// Add captured photos to the training photo library
|
||
async function addCapturedPhotosToLibrary(capturedPhotos) {
|
||
console.log('📸 addCapturedPhotosToLibrary called with', capturedPhotos.length, 'photos');
|
||
console.log('📸 desktopFileManager available:', !!window.desktopFileManager);
|
||
console.log('📸 isElectron:', window.desktopFileManager?.isElectron);
|
||
|
||
const newPhotos = [];
|
||
|
||
for (const [index, photo] of capturedPhotos.entries()) {
|
||
if (photo.dataURL) {
|
||
console.log(`📸 Processing photo ${index + 1}/${capturedPhotos.length}`);
|
||
|
||
// Save photo to file system if in Electron
|
||
let photoData;
|
||
if (window.desktopFileManager && window.desktopFileManager.isElectron) {
|
||
console.log('📸 Saving to file system...');
|
||
photoData = await window.desktopFileManager.savePhoto(photo.dataURL, 'training-academy');
|
||
|
||
if (!photoData) {
|
||
console.error('📸 Failed to save photo to file system - using fallback');
|
||
// Fallback to data URL
|
||
photoData = {
|
||
filename: `captured_photo_${Date.now()}_${index}.jpg`,
|
||
path: photo.dataURL,
|
||
fullPath: photo.dataURL,
|
||
isWebcamCapture: true,
|
||
timestamp: photo.timestamp || Date.now(),
|
||
sessionType: 'training-academy',
|
||
imageData: photo.dataURL
|
||
};
|
||
}
|
||
} else {
|
||
console.log('📸 Using browser fallback (data URL)');
|
||
// Browser fallback - use data URL
|
||
photoData = {
|
||
filename: `captured_photo_${Date.now()}_${index}.jpg`,
|
||
path: photo.dataURL,
|
||
fullPath: photo.dataURL,
|
||
isWebcamCapture: true,
|
||
timestamp: photo.timestamp || Date.now(),
|
||
sessionType: 'training-academy',
|
||
imageData: photo.dataURL
|
||
};
|
||
}
|
||
|
||
newPhotos.push(photoData);
|
||
trainingPhotoLibrary.push(photoData);
|
||
} else {
|
||
console.warn('📸 Photo missing dataURL:', photo);
|
||
}
|
||
}
|
||
|
||
console.log(`📸 Added ${newPhotos.length} photos to training library`);
|
||
|
||
// Update photo library status
|
||
const statusEl = document.getElementById('photoLibraryStatus');
|
||
if (statusEl) {
|
||
statusEl.innerHTML = `<span style="color: var(--color-success);">✅ ${trainingPhotoLibrary.length} photos available (${newPhotos.length} newly captured)</span>`;
|
||
}
|
||
}
|
||
|
||
// Show captured photos in a gallery view
|
||
function showCapturedPhotos() {
|
||
const webcamPhotos = trainingPhotoLibrary.filter(photo => photo.isWebcamCapture);
|
||
|
||
if (webcamPhotos.length === 0) {
|
||
alert('No captured photos found in the gallery.');
|
||
return;
|
||
}
|
||
|
||
const galleryHtml = `
|
||
<div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 10000; display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
||
<h2 style="color: white; margin-bottom: 20px;">📸 Captured Photos Gallery</h2>
|
||
<div style="display: flex; flex-wrap: wrap; gap: 15px; max-width: 90%; max-height: 70%; overflow-y: auto; justify-content: center;">
|
||
${webcamPhotos.map((photo, index) => {
|
||
// Use file URL if available, otherwise fallback to path (data URL for browser mode)
|
||
const imageSrc = photo.url || photo.path;
|
||
return `
|
||
<div style="border: 2px solid var(--text-white); border-radius: 10px; overflow: hidden;">
|
||
<img src="${imageSrc}" style="width: 200px; height: 150px; object-fit: cover;" alt="Captured photo ${index + 1}">
|
||
<div style="background: rgba(0,0,0,0.8); color: white; padding: 5px; text-align: center; font-size: 12px;">
|
||
Photo ${index + 1} - ${new Date(photo.timestamp).toLocaleTimeString()}
|
||
</div>
|
||
</div>
|
||
`}).join('')}
|
||
</div>
|
||
<button onclick="this.parentElement.remove()" style="margin-top: 20px; padding: 10px 20px; background: var(--color-danger); color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px;">
|
||
❌ Close Gallery
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
document.body.insertAdjacentHTML('beforeend', galleryHtml);
|
||
}
|
||
|
||
// Show all photos in the training library
|
||
function showAllPhotos() {
|
||
// Get verification photos from localStorage
|
||
const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]');
|
||
|
||
if (trainingPhotoLibrary.length === 0 && verificationPhotos.length === 0) {
|
||
alert('No photos found in the gallery.');
|
||
return;
|
||
}
|
||
|
||
// Separate photos by type
|
||
const webcamPhotos = trainingPhotoLibrary.filter(photo => photo.isWebcamCapture && photo.type !== 'position_verification');
|
||
const verificationPhotosFromLibrary = trainingPhotoLibrary.filter(photo => photo.type === 'position_verification');
|
||
const filePhotos = trainingPhotoLibrary.filter(photo => !photo.isWebcamCapture);
|
||
|
||
console.log('📸 Gallery photos breakdown:', {
|
||
webcamPhotos: webcamPhotos.length,
|
||
verificationFromLibrary: verificationPhotosFromLibrary.length,
|
||
verificationFromStorage: verificationPhotos.length,
|
||
filePhotos: filePhotos.length,
|
||
totalInLibrary: trainingPhotoLibrary.length
|
||
});
|
||
|
||
console.log('📸 Sample verification photo from library:', verificationPhotosFromLibrary[0]);
|
||
console.log('📸 Sample webcam photo:', webcamPhotos[0]);
|
||
|
||
const galleryHtml = `
|
||
<div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 10000; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; overflow-y: auto; padding: 20px;">
|
||
<h2 style="color: white; margin-bottom: 20px;">📸 Complete Photo Gallery</h2>
|
||
|
||
${verificationPhotosFromLibrary.length > 0 ? `
|
||
<div style="width: 100%; max-width: 1200px;">
|
||
<h3 style="color: var(--color-danger); margin-bottom: 15px;">🔍 Position Verification Photos (${verificationPhotosFromLibrary.length})</h3>
|
||
<div style="display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 30px; justify-content: center;">
|
||
${verificationPhotosFromLibrary.slice(-10).reverse().map((photo, index) => `
|
||
<div style="border: 2px solid var(--color-danger); border-radius: 10px; overflow: hidden; max-width: 250px;">
|
||
<img src="${photo.path}" style="width: 100%; height: 150px; object-fit: cover;" alt="Verification photo ${index + 1}">
|
||
<div style="background: rgba(231,76,60,0.8); color: white; padding: 8px; text-align: center; font-size: 11px; line-height: 1.2;">
|
||
<div style="font-weight: bold; margin-bottom: 3px;">${photo.phase === 'start' ? '📍 Start' : '🏁 End'} Verification</div>
|
||
<div style="font-size: 10px; opacity: 0.9;">${new Date(photo.timestamp).toLocaleDateString()} ${new Date(photo.timestamp).toLocaleTimeString()}</div>
|
||
${photo.message ? `<div style="margin-top: 5px; font-style: italic; font-size: 10px; border-top: 1px solid rgba(255,255,255,0.3); padding-top: 3px;">${photo.message.replace('📸 *SNAP* - ', '')}</div>` : ''}
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
${verificationPhotosFromLibrary.length > 10 ? `<p style="color: var(--text-muted); text-align: center; margin-bottom: 20px;">Showing last 10 verification photos (${verificationPhotosFromLibrary.length} total)</p>` : ''}
|
||
</div>
|
||
` : ''}
|
||
|
||
${webcamPhotos.length > 0 ? `
|
||
<div style="width: 100%; max-width: 1200px;">
|
||
<h3 style="color: var(--color-pink); margin-bottom: 15px;">📷 Other Webcam Photos (${webcamPhotos.length})</h3>
|
||
<div style="display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 30px; justify-content: center;">
|
||
${webcamPhotos.map((photo, index) => `
|
||
<div style="border: 2px solid var(--color-pink); border-radius: 10px; overflow: hidden;">
|
||
<img src="${photo.path}" style="width: 200px; height: 150px; object-fit: cover;" alt="Captured photo ${index + 1}">
|
||
<div style="background: rgba(255,20,147,0.8); color: white; padding: 5px; text-align: center; font-size: 12px;">
|
||
Captured - ${new Date(photo.timestamp).toLocaleDateString()}
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
|
||
${filePhotos.length > 0 ? `
|
||
<div style="width: 100%; max-width: 1200px;">
|
||
<h3 style="color: var(--color-success); margin-bottom: 15px;">📁 Library Photos (${filePhotos.length})</h3>
|
||
<div style="display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 20px; justify-content: center;">
|
||
${filePhotos.slice(0, 20).map((photo, index) => `
|
||
<div style="border: 2px solid var(--color-success); border-radius: 10px; overflow: hidden;">
|
||
<img src="file://${photo.fullPath}" style="width: 200px; height: 150px; object-fit: cover;" alt="Library photo ${index + 1}">
|
||
<div style="background: rgba(39,174,96,0.8); color: white; padding: 5px; text-align: center; font-size: 12px;">
|
||
${photo.filename}
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
${filePhotos.length > 20 ? `<p style="color: var(--text-muted); text-align: center;">... and ${filePhotos.length - 20} more photos</p>` : ''}
|
||
</div>
|
||
` : ''}
|
||
|
||
<button onclick="this.parentElement.remove()" style="margin-top: 20px; padding: 10px 20px; background: var(--color-danger); color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px;">
|
||
❌ Close Gallery
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
document.body.insertAdjacentHTML('beforeend', galleryHtml);
|
||
}
|
||
|
||
// Debug function - call this from console to inspect photo storage
|
||
window.debugPhotoStorage = function() {
|
||
const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]');
|
||
const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
|
||
|
||
console.log('=== PHOTO STORAGE DEBUG ===');
|
||
console.log('Verification Photos:', verificationPhotos.length);
|
||
console.log('Captured Photos:', capturedPhotos.length);
|
||
console.log('Training Photo Library:', trainingPhotoLibrary.length);
|
||
|
||
console.log('\nVerification Photos Sample:');
|
||
verificationPhotos.slice(-3).forEach((photo, i) => {
|
||
console.log(`${i+1}.`, {
|
||
timestamp: photo.timestamp,
|
||
phase: photo.phase,
|
||
type: photo.type,
|
||
hasData: !!photo.data,
|
||
dataLength: photo.data?.length
|
||
});
|
||
});
|
||
|
||
console.log('\nCaptured Photos Sample:');
|
||
capturedPhotos.slice(-3).forEach((photo, i) => {
|
||
console.log(`${i+1}.`, {
|
||
timestamp: photo.timestamp,
|
||
type: photo.type,
|
||
phase: photo.phase,
|
||
hasData: !!photo.data,
|
||
dataLength: photo.data?.length,
|
||
isWebcamCapture: photo.isWebcamCapture
|
||
});
|
||
});
|
||
|
||
console.log('\nTraining Library Sample:');
|
||
trainingPhotoLibrary.slice(-3).forEach((photo, i) => {
|
||
console.log(`${i+1}.`, {
|
||
filename: photo.filename,
|
||
type: photo.type,
|
||
phase: photo.phase,
|
||
isWebcamCapture: photo.isWebcamCapture,
|
||
hasPath: !!photo.path
|
||
});
|
||
});
|
||
};
|
||
|
||
// Test function for verification photo capture
|
||
function testVerificationPhoto() {
|
||
console.log('🧪 Testing verification photo capture...');
|
||
|
||
// Check if webcam manager is available
|
||
if (!window.game || !window.game.webcamManager) {
|
||
alert('Webcam manager not available. Start a game session first.');
|
||
return;
|
||
}
|
||
|
||
// Request camera access and test photo capture
|
||
window.game.webcamManager.requestCameraAccess().then(success => {
|
||
if (success) {
|
||
console.log('📷 Camera access granted, creating test overlay...');
|
||
|
||
// Create a simple test overlay
|
||
const testOverlay = document.createElement('div');
|
||
testOverlay.id = 'verification-test-overlay';
|
||
testOverlay.style.cssText = `
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0,0,0,0.9);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 99999;
|
||
`;
|
||
testOverlay.innerHTML = `
|
||
<div style="background: rgba(0,0,0,0.9); padding: 20px; border-radius: 10px; border: 2px solid var(--color-danger);">
|
||
<h3 style="color: white; margin-bottom: 15px; text-align: center;">🧪 Testing Photo Capture</h3>
|
||
<video id="verification-video" autoplay muted playsinline style="width: 300px; height: 200px; border: 2px solid var(--color-danger); border-radius: 8px; transform: scaleX(-1);"></video>
|
||
<div id="verification-status" style="color: white; margin-top: 10px; text-align: center;">Preparing test...</div>
|
||
<div style="text-align: center; margin-top: 15px;">
|
||
<button id="close-verification-test" style="padding: 8px 16px; background: var(--color-danger); color: white; border: none; border-radius: 5px; cursor: pointer;">Close Test</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
document.body.appendChild(testOverlay);
|
||
|
||
// Add proper close button handler
|
||
const closeButton = testOverlay.querySelector('#close-verification-test');
|
||
closeButton.addEventListener('click', () => {
|
||
console.log('🧪 Closing verification test overlay...');
|
||
if (testOverlay.parentNode) {
|
||
testOverlay.parentNode.removeChild(testOverlay);
|
||
}
|
||
document.removeEventListener('keydown', escapeHandler);
|
||
});
|
||
|
||
// Add escape key handler for emergency close
|
||
const escapeHandler = (event) => {
|
||
if (event.key === 'Escape') {
|
||
console.log('🧪 Emergency closing verification test with Escape key...');
|
||
if (testOverlay && testOverlay.parentNode) {
|
||
testOverlay.parentNode.removeChild(testOverlay);
|
||
}
|
||
document.removeEventListener('keydown', escapeHandler);
|
||
}
|
||
};
|
||
document.addEventListener('keydown', escapeHandler);
|
||
|
||
const testVideo = testOverlay.querySelector('#verification-video');
|
||
if (window.game.webcamManager.stream) {
|
||
testVideo.srcObject = window.game.webcamManager.stream;
|
||
|
||
// Wait for video to be ready then test photo capture
|
||
testVideo.onloadedmetadata = () => {
|
||
setTimeout(() => {
|
||
console.log('🧪 Testing photo capture...');
|
||
window.game.webcamManager.capturePositionPhoto('start', testOverlay, () => {
|
||
console.log('🧪 Test photo captured successfully!');
|
||
const statusElement = testOverlay.querySelector('#verification-status');
|
||
if (statusElement) {
|
||
statusElement.innerHTML = '✅ Test photo captured! Check the gallery.<br><small>This overlay will auto-close in 5 seconds...</small>';
|
||
statusElement.style.color = '#27ae60';
|
||
}
|
||
|
||
// Auto-close after showing success message
|
||
setTimeout(() => {
|
||
console.log('🧪 Auto-closing test overlay...');
|
||
if (testOverlay && testOverlay.parentNode) {
|
||
testOverlay.parentNode.removeChild(testOverlay);
|
||
}
|
||
|
||
// Refresh gallery button after overlay is closed
|
||
initializePhotoLibrary();
|
||
}, 5000);
|
||
});
|
||
}, 2000);
|
||
};
|
||
}
|
||
} else {
|
||
alert('Camera access required for photo capture test.');
|
||
}
|
||
}).catch(error => {
|
||
console.error('🧪 Camera test failed:', error);
|
||
alert('Failed to access camera: ' + error.message);
|
||
});
|
||
}
|
||
|
||
// ===== SCENARIO XP STATUS BAR FUNCTIONS =====
|
||
|
||
function showScenarioStatusBar() {
|
||
const statusBar = document.getElementById('scenario-status-bar');
|
||
if (statusBar) {
|
||
statusBar.style.display = 'flex';
|
||
console.log('📊 Scenario status bar displayed');
|
||
}
|
||
}
|
||
|
||
function hideScenarioStatusBar() {
|
||
const statusBar = document.getElementById('scenario-status-bar');
|
||
if (statusBar) {
|
||
statusBar.style.display = 'none';
|
||
console.log('📊 Scenario status bar hidden');
|
||
}
|
||
}
|
||
|
||
function updateScenarioStatusBar() {
|
||
if (!window.game || !window.game.gameState) return;
|
||
|
||
// Update timer
|
||
const timerElement = document.getElementById('scenario-timer');
|
||
if (timerElement && window.game.gameState.startTime) {
|
||
const elapsed = Date.now() - window.game.gameState.startTime;
|
||
const minutes = Math.floor(elapsed / 60000);
|
||
const seconds = Math.floor((elapsed % 60000) / 1000);
|
||
timerElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||
}
|
||
|
||
// Update XP display
|
||
const xpElement = document.getElementById('scenario-current-xp');
|
||
if (xpElement) {
|
||
const currentXP = getCurrentScenarioXP();
|
||
xpElement.textContent = currentXP;
|
||
}
|
||
|
||
// Update activity status
|
||
const activityElement = document.getElementById('scenario-activity');
|
||
if (activityElement) {
|
||
activityElement.textContent = getScenarioActivityStatus();
|
||
}
|
||
}
|
||
|
||
function getCurrentScenarioXP() {
|
||
if (!window.game || !window.game.gameState || !window.game.gameState.scenarioXp) {
|
||
return 0;
|
||
}
|
||
return window.game.gameState.scenarioXp.total || 0;
|
||
}
|
||
|
||
function getScenarioActivityStatus() {
|
||
if (!window.game || !window.game.gameState || !window.game.gameState.scenarioTracking) {
|
||
return 'Ready';
|
||
}
|
||
|
||
const tracking = window.game.gameState.scenarioTracking;
|
||
if (tracking.isInWebcamActivity) {
|
||
return 'Webcam Active';
|
||
} else if (tracking.isInFocusActivity) {
|
||
return 'Focus Task';
|
||
} else if (window.game.gameState.isRunning) {
|
||
return 'In Progress';
|
||
} else {
|
||
return 'Ready';
|
||
}
|
||
}
|
||
|
||
function startScenarioXPTracking() {
|
||
// Clear any existing interval
|
||
if (window.scenarioXPInterval) {
|
||
clearInterval(window.scenarioXPInterval);
|
||
}
|
||
|
||
// Update status bar every second and trigger XP calculations
|
||
window.scenarioXPInterval = setInterval(() => {
|
||
// Manually trigger scenario XP updates (since timer might be overridden)
|
||
if (window.game && window.game.gameState && window.game.gameState.isRunning) {
|
||
if (window.game.updateScenarioTimeBasedXp) {
|
||
window.game.updateScenarioTimeBasedXp();
|
||
}
|
||
if (window.game.updateScenarioFocusXp) {
|
||
window.game.updateScenarioFocusXp();
|
||
}
|
||
if (window.game.updateScenarioWebcamXp) {
|
||
window.game.updateScenarioWebcamXp();
|
||
}
|
||
}
|
||
|
||
updateScenarioStatusBar();
|
||
}, 1000);
|
||
|
||
console.log('📊 Started scenario XP tracking');
|
||
}
|
||
|
||
function stopScenarioXPTracking() {
|
||
if (window.scenarioXPInterval) {
|
||
clearInterval(window.scenarioXPInterval);
|
||
window.scenarioXPInterval = null;
|
||
console.log('📊 Stopped scenario XP tracking');
|
||
}
|
||
}
|
||
|
||
function saveScenarioXPToPlayerStats() {
|
||
if (!window.game || !window.game.gameState || !window.game.gameState.scenarioXp) {
|
||
console.log('📊 No scenario XP to save');
|
||
return;
|
||
}
|
||
|
||
const totalScenarioXP = window.game.gameState.scenarioXp.total || 0;
|
||
|
||
if (totalScenarioXP > 0) {
|
||
// Award XP to player stats system
|
||
if (window.playerStats) {
|
||
window.playerStats.awardXP(totalScenarioXP, 'scenario');
|
||
console.log(`📊 Awarded ${totalScenarioXP} scenario XP to player stats`);
|
||
}
|
||
|
||
// Also add to overall XP counter (for compatibility with existing system)
|
||
if (window.game.dataManager) {
|
||
const overallXp = window.game.dataManager.get('overallXp') || 0;
|
||
const newOverallXp = overallXp + totalScenarioXP;
|
||
window.game.dataManager.set('overallXp', newOverallXp);
|
||
console.log(`📊 Added ${totalScenarioXP} XP to overall counter: ${overallXp} → ${newOverallXp}`);
|
||
}
|
||
} else {
|
||
console.log('📊 No scenario XP earned this session');
|
||
}
|
||
}
|
||
|
||
// Test function to check XP awarding
|
||
window.testXPAwarding = function() {
|
||
console.log('🧪 Testing XP awarding system...');
|
||
|
||
// Check PlayerStats availability
|
||
console.log('📊 PlayerStats available:', !!window.playerStats);
|
||
if (window.playerStats) {
|
||
console.log('📊 Current total XP:', window.playerStats.stats.totalXP);
|
||
console.log('📊 Current scenario XP:', window.playerStats.stats.scenarioGameXP);
|
||
}
|
||
|
||
// Check game state
|
||
console.log('🎮 Game available:', !!window.game);
|
||
if (window.game && window.game.gameState) {
|
||
console.log('🎮 Scenario XP in game state:', window.game.gameState.scenarioXp);
|
||
console.log('🎮 Photos taken this session:', window.game.gameState.photosTaken || 0);
|
||
|
||
if (window.game.gameState.scenarioXp) {
|
||
console.log('📸 Photo XP in scenario:', window.game.gameState.scenarioXp.photoRewards || 0);
|
||
}
|
||
}
|
||
|
||
// Test awarding 10 XP directly
|
||
if (window.playerStats) {
|
||
const beforeXP = window.playerStats.stats.totalXP;
|
||
window.playerStats.awardXP(10, 'scenario');
|
||
const afterXP = window.playerStats.stats.totalXP;
|
||
console.log(`🧪 Test XP award: ${beforeXP} → ${afterXP} (+${afterXP - beforeXP})`);
|
||
}
|
||
|
||
// Test photo XP via game system
|
||
if (window.game && window.game.incrementPhotosTaken) {
|
||
console.log('🧪 Testing photo XP via game.incrementPhotosTaken()...');
|
||
const beforeScenarioXP = window.game.gameState.scenarioXp?.photoRewards || 0;
|
||
window.game.incrementPhotosTaken();
|
||
const afterScenarioXP = window.game.gameState.scenarioXp?.photoRewards || 0;
|
||
console.log(`📸 Photo XP test: ${beforeScenarioXP} → ${afterScenarioXP} (+${afterScenarioXP - beforeScenarioXP})`);
|
||
}
|
||
|
||
return {
|
||
success: true,
|
||
playerStats: !!window.playerStats,
|
||
game: !!window.game,
|
||
currentTotalXP: window.playerStats?.stats.totalXP || 0,
|
||
currentScenarioXP: window.playerStats?.stats.scenarioGameXP || 0,
|
||
photosTaken: window.game?.gameState?.photosTaken || 0,
|
||
scenarioPhotoXP: window.game?.gameState?.scenarioXp?.photoRewards || 0
|
||
};
|
||
};
|
||
|
||
// Test function to check webcam manager game connection
|
||
window.testWebcamGameConnection = function() {
|
||
console.log('🧪 Testing webcam manager game connection...');
|
||
|
||
if (currentWebcamManager) {
|
||
console.log('📷 WebcamManager available:', !!currentWebcamManager);
|
||
console.log('📷 WebcamManager has game reference:', !!currentWebcamManager.game);
|
||
if (currentWebcamManager.game) {
|
||
console.log('🎮 Game incrementPhotosTaken available:', !!currentWebcamManager.game.incrementPhotosTaken);
|
||
}
|
||
return {
|
||
webcamManager: true,
|
||
hasGameReference: !!currentWebcamManager.game,
|
||
hasIncrementPhotosTaken: !!(currentWebcamManager.game && currentWebcamManager.game.incrementPhotosTaken)
|
||
};
|
||
} else {
|
||
console.log('❌ No webcam manager available');
|
||
return { webcamManager: false };
|
||
}
|
||
};
|
||
</script>
|
||
</body>
|
||
</html> |