training-academy/training-academy.html

7373 lines
330 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 Modules</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">
<link rel="stylesheet" href="src/styles/academy-ui.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;
}
/* Module Cards */
.training-mode-card.locked {
opacity: 0.5;
cursor: not-allowed;
border-color: var(--border-subtle);
}
.training-mode-card.locked::after {
content: '🔒 LOCKED';
position: absolute;
top: 10px;
right: 10px;
background: var(--bg-danger);
color: var(--text-on-danger);
padding: 4px 8px;
border-radius: 4px;
font-size: 0.75em;
font-weight: bold;
}
.training-mode-card.coming-soon::after {
content: 'COMING SOON';
position: absolute;
top: 10px;
right: 10px;
background: var(--bg-secondary-overlay-30);
color: var(--color-primary);
padding: 4px 8px;
border-radius: 4px;
font-size: 0.75em;
font-weight: bold;
}
.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);
}
/* Worship Session Unique Styling - Altar/Shrine Layout */
.worship-mode .scenario-choice,
.worship-mode .scenario-action-interactive,
.worship-mode .scenario-text {
background: transparent;
border: none;
box-shadow: none;
padding: 40px 20px;
max-width: 1000px;
margin: 0 auto;
}
/* Worship Altar Container */
.worship-mode .worship-altar {
display: flex;
flex-direction: column;
align-items: center;
gap: 30px;
}
/* Image Shrine at Top */
.worship-mode .worship-shrine {
width: 100%;
max-width: 800px;
height: 500px;
background: linear-gradient(to bottom, rgba(218, 165, 32, 0.1), transparent);
border: 3px solid rgba(218, 165, 32, 0.3);
border-radius: 10px;
padding: 20px;
box-shadow: 0 10px 50px rgba(218, 165, 32, 0.2), inset 0 0 100px rgba(218, 165, 32, 0.05);
position: relative;
overflow: hidden;
}
.worship-mode .worship-shrine::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #DAA520, transparent);
opacity: 0.5;
}
.worship-mode .worship-shrine::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #DAA520, transparent);
opacity: 0.5;
}
/* Scripture/Prayer Text Below */
.worship-mode .scenario-story {
background: transparent;
border: none;
border-top: 1px solid rgba(218, 165, 32, 0.3);
border-bottom: 1px solid rgba(218, 165, 32, 0.3);
padding: 30px 60px;
text-align: center;
font-family: 'Georgia', serif;
font-size: 1.3em;
line-height: 2;
color: rgba(218, 165, 32, 0.9);
font-style: italic;
max-width: 800px;
margin: 0 auto;
text-shadow: 0 0 20px rgba(218, 165, 32, 0.3);
}
.worship-mode h3 {
display: none; /* Hide title, let the imagery speak */
}
/* Offerings (Action Buttons) */
.worship-mode .scenario-choices {
display: flex;
flex-direction: column;
gap: 20px;
width: 100%;
max-width: 600px;
margin: 40px auto 0;
}
.worship-mode .choice-option {
background: linear-gradient(135deg, rgba(10, 5, 20, 0.8), rgba(20, 10, 30, 0.6));
border: 2px solid rgba(218, 165, 32, 0.4);
border-radius: 0;
padding: 25px 40px;
text-align: center;
transition: all 0.4s ease;
position: relative;
overflow: hidden;
}
.worship-mode .choice-option::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: linear-gradient(to bottom, transparent, #DAA520, transparent);
opacity: 0;
transition: opacity 0.4s ease;
}
.worship-mode .choice-option::after {
content: '';
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 3px;
background: linear-gradient(to bottom, transparent, #DAA520, transparent);
opacity: 0;
transition: opacity 0.4s ease;
}
.worship-mode .choice-option:hover {
background: linear-gradient(135deg, rgba(218, 165, 32, 0.15), rgba(255, 215, 0, 0.1));
border-color: rgba(218, 165, 32, 0.8);
box-shadow: 0 0 40px rgba(218, 165, 32, 0.3);
transform: scale(1.02);
}
.worship-mode .choice-option:hover::before,
.worship-mode .choice-option:hover::after {
opacity: 1;
}
.worship-mode .choice-option h4 {
color: #DAA520;
font-size: 1.5em;
font-family: 'Georgia', serif;
margin: 0 0 10px 0;
letter-spacing: 3px;
}
.worship-mode .choice-preview {
color: rgba(218, 165, 32, 0.7);
font-style: italic;
font-size: 0.95em;
}
/* Continue/Action Buttons as Offerings */
.worship-mode .training-controls {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
margin-top: 50px;
}
.worship-mode .btn-primary,
.worship-mode .complete-btn,
.worship-mode .action-btn {
background: transparent;
border: 2px solid rgba(218, 165, 32, 0.6);
color: #DAA520;
font-family: 'Georgia', serif;
font-size: 1.1em;
padding: 15px 50px;
letter-spacing: 2px;
text-transform: uppercase;
font-weight: normal;
text-shadow: none;
transition: all 0.4s ease;
}
.worship-mode .btn-primary:hover,
.worship-mode .complete-btn:hover,
.worship-mode .action-btn:hover {
background: rgba(218, 165, 32, 0.1);
border-color: #DAA520;
box-shadow: 0 0 30px rgba(218, 165, 32, 0.4);
transform: translateY(-2px);
}
.worship-mode .next-btn {
background: transparent;
border: 1px solid rgba(218, 165, 32, 0.3);
color: rgba(218, 165, 32, 0.5);
font-size: 0.9em;
padding: 10px 30px;
}
/* Slideshow in Worship Mode - Full Screen Altar */
.worship-mode .preference-slideshow-task {
background: transparent;
padding: 0;
}
.worship-mode .preference-slideshow-task h3 {
display: block;
text-align: center;
color: #DAA520;
font-family: 'Georgia', serif;
font-size: 1.5em;
letter-spacing: 3px;
margin-bottom: 30px;
text-shadow: 0 0 20px rgba(218, 165, 32, 0.5);
}
.worship-mode .preference-slideshow-task p {
text-align: center;
color: rgba(218, 165, 32, 0.7);
font-style: italic;
margin-bottom: 30px;
}
.worship-mode #slideshow-container {
max-width: 100% !important;
height: 600px !important;
border: 3px solid rgba(218, 165, 32, 0.4);
border-radius: 0;
box-shadow: 0 10px 60px rgba(218, 165, 32, 0.3), inset 0 0 100px rgba(218, 165, 32, 0.05);
}
.worship-mode #slideshow-caption {
color: #DAA520 !important;
text-shadow: 2px 2px 8px rgba(0,0,0,0.9),
0 0 40px rgba(218, 165, 32, 0.8) !important;
font-family: 'Georgia', serif !important;
font-style: italic;
}
/* Ornamental Separators */
.worship-mode .ornament-separator {
text-align: center;
margin: 40px 0;
color: rgba(218, 165, 32, 0.4);
font-size: 1.5em;
letter-spacing: 20px;
}
/* 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);
}
.features-unlocked {
background: linear-gradient(135deg, rgba(131, 56, 236, 0.3), rgba(58, 134, 255, 0.2));
border: 2px solid var(--color-primary);
border-radius: 12px;
padding: 20px;
margin: 20px 0;
animation: unlockGlow 2s ease-in-out infinite;
}
.features-unlocked h4 {
color: var(--color-primary);
margin-bottom: 15px;
font-size: 1.3em;
}
.features-unlocked ul {
list-style: none;
padding: 0;
margin: 0;
}
.features-unlocked li {
background: rgba(0, 0, 0, 0.5);
padding: 10px 15px;
margin: 8px 0;
border-radius: 6px;
border-left: 3px solid var(--color-success);
font-size: 1.1em;
}
@keyframes unlockGlow {
0%, 100% { box-shadow: 0 0 20px rgba(131, 56, 236, 0.3); }
50% { box-shadow: 0 0 30px rgba(131, 56, 236, 0.6); }
}
/* Features Panel */
.features-panel {
position: fixed;
top: 80px;
right: 20px;
z-index: 10000;
}
.toggle-features-btn {
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
color: white;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
padding: 12px 20px;
font-size: 1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.toggle-features-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(131, 56, 236, 0.5);
}
.features-list {
position: absolute;
top: 60px;
right: 0;
background: rgba(20, 20, 30, 0.98);
border: 2px solid var(--color-primary);
border-radius: 12px;
padding: 20px;
min-width: 320px;
max-width: 400px;
max-height: 600px;
overflow-y: auto;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
}
.features-list h4 {
color: var(--color-primary);
margin: 0 0 15px 0;
font-size: 1.3em;
border-bottom: 2px solid var(--color-primary);
padding-bottom: 10px;
}
.feature-item {
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(131, 56, 236, 0.3);
border-radius: 8px;
padding: 12px;
margin: 10px 0;
transition: all 0.3s ease;
}
.feature-item:hover {
border-color: var(--color-primary);
background: rgba(131, 56, 236, 0.1);
}
.feature-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.feature-name {
font-size: 1.1em;
font-weight: 600;
color: var(--text-primary);
}
.feature-toggle {
position: relative;
width: 50px;
height: 26px;
}
.feature-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.feature-toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(100, 100, 100, 0.5);
transition: 0.3s;
border-radius: 34px;
border: 2px solid rgba(255, 255, 255, 0.2);
}
.feature-toggle-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
background-color: white;
transition: 0.3s;
border-radius: 50%;
}
.feature-toggle input:checked + .feature-toggle-slider {
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
border-color: var(--color-primary);
}
.feature-toggle input:checked + .feature-toggle-slider:before {
transform: translateX(24px);
}
.feature-description {
font-size: 0.9em;
color: var(--text-secondary);
margin-top: 5px;
}
.feature-locked {
opacity: 0.5;
cursor: not-allowed;
}
.feature-locked .feature-toggle {
pointer-events: none;
}
.no-features-msg {
text-align: center;
color: var(--text-secondary);
padding: 20px;
font-style: italic;
}
.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 Modules</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">
<!-- Game Stats Panel (new) -->
<div style="background: var(--bg-secondary); border: 2px solid var(--color-success); border-radius: 8px; padding: 15px; margin-bottom: 15px; display: none;" id="game-stats-panel">
<div style="color: var(--text-muted); font-size: 0.9em; margin-bottom: 10px; text-align: center;">Current Session</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<div style="text-align: center;">
<div style="color: var(--text-muted); font-size: 0.8em;">XP Earned</div>
<div style="font-size: 1.3em; font-weight: bold; color: var(--color-success);" id="sidebar-current-xp">0</div>
</div>
</div>
</div>
<!-- Countdown Timer (for tasks with showTimer) -->
<div style="background: var(--bg-secondary); border: 2px solid var(--color-primary); border-radius: 8px; padding: 15px; text-align: center; display: none;" id="sidebar-countdown-timer">
<div style="color: var(--text-muted); font-size: 0.9em; margin-bottom: 5px;">Time Remaining</div>
<div style="font-size: 2.5em; font-weight: bold; color: var(--color-primary);" id="sidebar-countdown-display">--:--</div>
</div>
<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 Modules - Module Selection</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>
<!-- Level Select Screen (Hidden initially) -->
<div id="levelSelectScreen" class="level-select-screen" style="display: none;">
<div class="level-select-header">
<h2>🎓 Campaign Mode - Select Level</h2>
<p class="progress-subtitle">Choose a level to begin or replay</p>
<div class="campaign-progress">
<div class="progress-bar-container">
<div id="campaign-progress-bar" class="progress-bar-fill" style="width: 0%"></div>
</div>
<p class="progress-text">Progress: <span id="campaign-progress-text">0 / 30 Levels Complete</span></p>
</div>
</div>
<div id="level-grid" class="level-grid">
<!-- Levels will be dynamically populated here -->
</div>
<div style="text-align: center; margin: 30px 0;">
<button id="back-to-setup-btn" class="btn btn-secondary" style="padding: 12px 30px; font-size: 1.1em;">
← Back to Setup
</button>
</div>
</div>
<!-- Game Interface (Hidden initially) -->
<div id="gameInterface" style="display: none;">
<!-- Feature Controls Panel -->
<div id="featuresPanel" class="features-panel" style="display: none;">
<button id="toggleFeaturesBtn" class="toggle-features-btn" onclick="toggleFeaturesPanel()">
⚙️ Features
</button>
<div id="featuresList" class="features-list" style="display: none;">
<h4>Unlocked Features</h4>
<div id="featuresContent">
<!-- Features will be injected here -->
</div>
</div>
</div>
<!-- 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>
<!-- Utility Scripts -->
<script src="src/utils/backupManager.js"></script>
<!-- Academy System Scripts -->
<script src="src/features/academy/campaignManager.js"></script>
<script src="src/features/academy/preferenceManager.js"></script>
<script src="src/features/academy/libraryManager.js"></script>
<script src="src/features/academy/academyLevelData.js"></script>
<script src="src/features/academy/academyUI.js"></script>
<!-- Inventory System Utilities -->
<!-- Inventory System Utilities -->
<script src="src/utils/inventoryManager.js"></script>
<script src="src/data/poseBanks/inventoryPoseBank.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 Gooner Training Modules. 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;
// Save to localStorage
const settings = loadAcademySettings();
settings.enableTTS = ttsEnabled;
saveAcademySettings(settings);
// 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 8 training modules with proper rank gating
const allModes = {
// TIER 1: Beginner (Rank 1 - Available immediately)
'photography-studio': {
name: 'Photo Session',
description: 'Webcam photography with dress-up challenges (20-40 min)',
icon: '📸',
tier: 1,
rank: 1,
available: true // Implemented
},
'goon-loop': {
name: 'Goon Loop',
description: 'Hypnotic, repetitive trance-inducing sessions (30+ min)',
icon: '🌀',
tier: 1,
rank: 1,
available: false // Not implemented yet
},
// TIER 2: Intermediate (Ranks 3-5)
'rhythm-training': {
name: 'Rhythm Training',
description: 'Metronome-guided stroking and beat matching (20-30 min)',
icon: '🎵',
tier: 2,
rank: 3,
available: false // Not implemented yet
},
'worship-session': {
name: 'Worship Session',
description: 'Devotional content worship (requires tagged library) (20-30 min)',
icon: '🙏',
tier: 2,
rank: 5,
available: true // Implemented
},
// TIER 3: Advanced (Ranks 7-8)
'joi-session': {
name: 'JOI Session',
description: 'TTS-guided instruction and task enforcement (20-30 min)',
icon: '🎙️',
tier: 3,
rank: 7,
available: false // Not implemented yet
},
'multi-screen': {
name: 'Multi-Screen',
description: 'Dual/quad video sensory overload (20-30 min)',
icon: '📺',
tier: 3,
rank: 8,
available: false // Not implemented yet
},
// TIER 4: Expert (Ranks 10-12)
'tease-denial': {
name: 'Tease & Denial',
description: 'Stop/start edge challenges and denial training (20-30 min)',
icon: '🎭',
tier: 4,
rank: 10,
available: false // Not implemented yet
},
'punishment-gauntlet': {
name: 'Humiliation',
description: 'Psychological degradation through interactive story (30-45 min)',
icon: '😈',
tier: 4,
rank: 12,
available: true // Implemented
}
};
// TODO: Add rank checking when player stats are integrated
// For now, return all 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}`);
// Sync trainingVideoLibrary to videoPlayerManager
if (window.videoPlayerManager && trainingVideoLibrary.length > 0) {
// Convert trainingVideoLibrary format to videoPlayerManager format
const videoPaths = trainingVideoLibrary.map(v => v.path || v.fullPath);
// Update all video categories to use the same library
window.videoPlayerManager.videoLibrary.background = videoPaths;
window.videoPlayerManager.videoLibrary.task = videoPaths;
window.videoPlayerManager.videoLibrary.reward = videoPaths;
window.videoPlayerManager.videoLibrary.punishment = videoPaths;
console.log(`🔄 Synced ${videoPaths.length} videos to videoPlayerManager`);
}
// Update status display (if element exists)
const statusEl = document.getElementById('videoLibraryStatus');
if (statusEl) {
if (trainingVideoLibrary.length > 0) {
statusEl.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 {
statusEl.innerHTML =
'<span style="color: var(--color-danger);">❌ No videos found in linked directories</span>';
}
}
} catch (error) {
console.error('❌ Error initializing video library:', error);
const statusEl = document.getElementById('videoLibraryStatus');
if (statusEl) {
statusEl.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 (Electron only)
try {
if (window.desktopFileManager && window.desktopFileManager.isElectron) {
console.log('📸 Loading captured photos from file system...');
const filePhotos = await window.desktopFileManager.loadCapturedPhotos();
if (filePhotos && filePhotos.length > 0) {
trainingPhotoLibrary.push(...filePhotos);
console.log(`📸 Loaded ${filePhotos.length} photos from file system (photos/captured/)`);
} else {
console.log('📸 No captured photos found in file system');
}
} else {
console.log('📸 Desktop file manager not available - browser mode (no persistent photo storage)');
}
} catch (error) {
console.error('⚠️ Failed to load captured photos from file system:', 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';
// Add locked or coming-soon class if not available
if (!mode.available) {
modeCard.classList.add('coming-soon');
}
modeCard.dataset.mode = modeId;
// Only allow click if available
if (mode.available) {
modeCard.onclick = () => selectTrainingMode(modeId);
} else {
modeCard.onclick = () => {
console.log(`🔒 ${mode.name} is not yet implemented`);
// TODO: Show unlock requirements when rank system is integrated
};
}
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', () => {
// For Training Academy mode, show level select
if (selectedTrainingMode === 'training-academy') {
showLevelSelectScreen();
} else {
// For other modes, start directly
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');
}
// ==========================================
// LEVEL SELECT SCREEN FUNCTIONS
// ==========================================
// Show Level Select Screen
function showLevelSelectScreen() {
console.log('🎯 Showing level select screen');
// Hide setup screen
const setupScreen = document.getElementById('academy-setup');
if (setupScreen) {
setupScreen.style.display = 'none';
}
// Show level select screen
const levelSelectScreen = document.getElementById('levelSelectScreen');
if (levelSelectScreen) {
levelSelectScreen.style.display = 'block';
}
// Populate level grid
populateLevelGrid();
// Setup back button
const backBtn = document.getElementById('back-to-setup-btn');
if (backBtn) {
backBtn.addEventListener('click', () => {
levelSelectScreen.style.display = 'none';
setupScreen.style.display = 'block';
});
}
}
// Populate Level Grid
function populateLevelGrid() {
const levelGrid = document.getElementById('level-grid');
if (!levelGrid) return;
// Get Academy scenarios
if (!window.trainingGameData || !window.trainingGameData.academyScenarios) {
console.error('❌ Academy scenarios not loaded');
levelGrid.innerHTML = '<p style="text-align: center; color: #ff006e;">Error: Academy scenarios not loaded. Please refresh.</p>';
return;
}
const scenarios = Object.values(window.trainingGameData.academyScenarios);
console.log(`📋 Populating ${scenarios.length} levels`);
// Get progress from campaignManager
const progress = window.campaignManager
? window.gameData.academyProgress
: loadAcademyProgress();
console.log('📊 Academy progress:', {
currentLevel: progress.currentLevel,
highestUnlocked: progress.highestUnlockedLevel,
completed: progress.completedLevels || Object.keys(progress.completed || {})
});
// Update progress bar
const completedCount = progress.completedLevels
? progress.completedLevels.length
: Object.keys(progress.completed || {}).length;
const totalLevels = scenarios.length;
const progressPercent = (completedCount / totalLevels) * 100;
const progressBar = document.getElementById('campaign-progress-bar');
const progressText = document.getElementById('campaign-progress-text');
if (progressBar) progressBar.style.width = `${progressPercent}%`;
if (progressText) progressText.textContent = `${completedCount} / ${totalLevels} Levels Complete`;
// Clear and populate grid
levelGrid.innerHTML = '';
scenarios.forEach((scenario, index) => {
const levelNum = scenario.level || (index + 1);
// Use campaignManager if available for unlock/complete status
let isCompleted, isUnlocked;
if (window.campaignManager) {
isCompleted = window.campaignManager.isLevelCompleted(levelNum);
isUnlocked = window.campaignManager.isLevelUnlocked(levelNum);
} else {
// Fallback to old system
isCompleted = progress.completed[scenario.id] || false;
isUnlocked = levelNum === 1 || progress.completed[scenarios[index - 1]?.id] || false;
}
const isCurrent = levelNum === (progress.currentLevel || 1);
const button = document.createElement('button');
button.className = 'level-button';
if (isCompleted) button.classList.add('completed');
else if (isCurrent) button.classList.add('current');
else if (isUnlocked) button.classList.add('unlocked');
button.disabled = !isUnlocked;
button.innerHTML = `
<div class="level-number">${levelNum}</div>
<div class="level-name">${scenario.name}</div>
<div class="level-arc">${scenario.arc}</div>
<div class="level-duration">${Math.floor(scenario.duration / 60)}m</div>
${isCompleted ? '<div class="level-status">✅</div>' : ''}
${isCurrent && !isCompleted ? '<div class="level-status">▶️</div>' : ''}
${!isUnlocked ? '<div class="level-status">🔒</div>' : ''}
`;
if (isUnlocked) {
button.addEventListener('click', () => {
startLevel(scenario, levelNum);
});
}
levelGrid.appendChild(button);
});
}
// Load Academy Progress
function loadAcademyProgress() {
const defaultProgress = {
currentLevel: 1,
completed: {}
};
try {
const saved = localStorage.getItem('trainingAcademyProgress');
if (saved) {
return { ...defaultProgress, ...JSON.parse(saved) };
}
} catch (error) {
console.error('Error loading academy progress:', error);
}
return defaultProgress;
}
// Save Academy Progress
function saveAcademyProgress(progress) {
try {
localStorage.setItem('trainingAcademyProgress', JSON.stringify(progress));
console.log('💾 Academy progress saved:', progress);
} catch (error) {
console.error('Error saving academy progress:', error);
}
}
// Start a specific level
function startLevel(scenario, levelNum) {
console.log(`🎓 Starting Level ${levelNum}: ${scenario.name}`);
// Store selected level and track start time
window.selectedAcademyLevel = scenario;
window.selectedLevelNumber = levelNum;
window.levelStartTime = Date.now();
// Hide level select screen
const levelSelectScreen = document.getElementById('levelSelectScreen');
if (levelSelectScreen) {
levelSelectScreen.style.display = 'none';
}
// Start training session with this specific level
startTrainingSession();
}
// ==========================================
// END LEVEL SELECT SCREEN FUNCTIONS
// ==========================================
// Return to Level Select Screen
function returnToLevelSelect() {
console.log('🔙 Returning to level select');
// Clear any active training state
currentScenarioTask = null;
currentScenarioStep = 'start';
// Clear selected level (but keep selected mode)
window.selectedAcademyLevel = null;
window.selectedLevelNumber = null;
// Clear any game state
if (window.game) {
window.game.gameState.isRunning = false;
window.game.gameState.isPaused = false;
window.game.gameState.currentTask = null;
window.game.currentTask = null;
// Save scenario XP to player stats before clearing
saveScenarioXPToPlayerStats();
// Hide status bar and stop XP tracking
hideScenarioStatusBar();
stopScenarioXPTracking();
}
// Stop background video
const bgVideo = document.getElementById('backgroundVideo');
if (bgVideo) {
bgVideo.pause();
bgVideo.src = '';
}
// Hide game interface
const gameInterface = document.getElementById('gameInterface');
if (gameInterface) {
gameInterface.style.display = 'none';
}
// Hide sidebar panels
const gameStatsPanel = document.getElementById('game-stats-panel');
console.log('📊 RETURN TO SELECT - Game stats panel element:', gameStatsPanel);
if (gameStatsPanel) {
console.log('📊 RETURN TO SELECT - Current display:', gameStatsPanel.style.display);
gameStatsPanel.style.display = 'none';
console.log('📊 RETURN TO SELECT - Set display to none');
}
const countdownTimer = document.getElementById('sidebar-countdown-timer');
console.log('⏱️ RETURN TO SELECT - Countdown timer element:', countdownTimer);
if (countdownTimer) {
console.log('⏱️ RETURN TO SELECT - Current display:', countdownTimer.style.display);
countdownTimer.style.display = 'none';
console.log('⏱️ RETURN TO SELECT - Set display to none');
}
// Hide features panel
const featuresPanel = document.getElementById('featuresPanel');
if (featuresPanel) {
featuresPanel.style.display = 'none';
}
const featuresList = document.getElementById('featuresList');
if (featuresList) {
featuresList.style.display = 'none';
}
// Clear game container
const gameContainer = document.getElementById('gameContainer');
if (gameContainer) {
gameContainer.innerHTML = '';
}
// Clear task display
const taskDisplay = document.getElementById('training-task-display');
if (taskDisplay) {
taskDisplay.innerHTML = '';
}
// Clear video players
if (window.videoPlayerManager) {
// Stop and hide all video players
if (window.videoPlayerManager.backgroundPlayer) {
window.videoPlayerManager.backgroundPlayer.pause();
window.videoPlayerManager.backgroundPlayer.src = '';
}
if (window.videoPlayerManager.currentPlayer) {
window.videoPlayerManager.currentPlayer.pause();
window.videoPlayerManager.currentPlayer.src = '';
window.videoPlayerManager.currentPlayer.style.display = 'none';
}
if (window.videoPlayerManager.overlayPlayer) {
window.videoPlayerManager.overlayPlayer.pause();
window.videoPlayerManager.overlayPlayer.src = '';
window.videoPlayerManager.overlayPlayer.style.display = 'none';
}
}
// Clear any PiP containers created by dual video
const pipContainers = document.querySelectorAll('div[style*="position: fixed"][style*="z-index: 100"]');
pipContainers.forEach(container => {
// Check if it contains a video element
if (container.querySelector('video')) {
container.remove();
}
});
// Clear any overlays
const overlays = document.querySelectorAll('.camera-overlay, .verification-overlay, #camera-overlay');
overlays.forEach(overlay => {
if (overlay) overlay.remove();
});
// Hide setup screen
const setupScreen = document.getElementById('academy-setup');
if (setupScreen) {
setupScreen.style.display = 'none';
}
// Show level select screen
showLevelSelectScreen();
}
// ==========================================
// FEATURES PANEL FUNCTIONS
// ==========================================
// Toggle features panel visibility
function toggleFeaturesPanel() {
const featuresList = document.getElementById('featuresList');
if (featuresList.style.display === 'none') {
featuresList.style.display = 'block';
updateFeaturesPanel();
} else {
featuresList.style.display = 'none';
}
}
// Update the features panel with unlocked features
function updateFeaturesPanel() {
const featuresContent = document.getElementById('featuresContent');
if (!featuresContent || !window.campaignManager) return;
const unlockedFeatures = window.campaignManager.getUnlockedFeatures();
const activeFeatures = loadActiveFeatures();
const featureDefinitions = {
'video': {
name: '🎬 Video Playback',
description: 'Play background videos during training',
setting: 'enableVideo'
},
'webcam': {
name: '📹 Webcam Mirror',
description: 'Show your webcam feed during sessions',
setting: 'enableWebcam'
},
'dual-video': {
name: '📺 Dual Video',
description: 'Play two videos simultaneously',
setting: 'enableDualVideo'
},
'tts': {
name: '🔊 Text-to-Speech',
description: 'Voice narration for tasks and instructions',
setting: 'enableTTS'
},
'quad-video': {
name: '🖥️ Quad Video',
description: 'Play four videos at once for sensory overload',
setting: 'enableQuadVideo'
},
'hypno-spiral': {
name: '🌀 Hypno Spiral',
description: 'Mesmerizing spiral overlay effects',
setting: 'enableHypnoSpiral'
},
'hypno-captions': {
name: '💬 Hypno Captions',
description: 'Subliminal messaging during training',
setting: 'enableHypnoCaptions'
},
'dynamic-captions': {
name: '✨ Dynamic Captions',
description: 'Animated text effects and transitions',
setting: 'enableDynamicCaptions'
},
'tts-hypno-sync': {
name: '🎵 TTS Hypno Sync',
description: 'Synchronize voice with hypno effects',
setting: 'enableTTSHypnoSync'
},
'interruptions': {
name: '⚡ Interruptions',
description: 'Random interruption events during training',
setting: 'enableInterruptions'
},
'denial-training': {
name: '🚫 Denial Training',
description: 'Advanced edge and denial mechanics',
setting: 'enableDenialTraining'
},
'popup-images': {
name: '🖼️ Popup Images',
description: 'Random image popups during sessions',
setting: 'enablePopupImages'
},
'edge-counter': {
name: '🔢 Edge Counter',
description: 'Track and display edge count',
setting: 'enableEdgeCounter'
},
'sensory-overload': {
name: '💥 Sensory Overload',
description: 'Maximum intensity with all effects',
setting: 'enableSensoryOverload'
},
'advanced-mode': {
name: '🎓 Advanced Mode',
description: 'Unlock all advanced training options',
setting: 'enableAdvancedMode'
}
};
if (unlockedFeatures.length === 0) {
featuresContent.innerHTML = `
<div class="no-features-msg">
Complete campaign levels to unlock features!
</div>
`;
return;
}
let html = '';
unlockedFeatures.forEach(featureId => {
const feature = featureDefinitions[featureId];
if (!feature) return;
const isActive = activeFeatures[featureId] !== false;
const checkboxId = `feature-${featureId}`;
html += `
<div class="feature-item">
<div class="feature-header">
<span class="feature-name">${feature.name}</span>
<label class="feature-toggle">
<input type="checkbox"
id="${checkboxId}"
${isActive ? 'checked' : ''}
onchange="toggleFeature('${featureId}', this.checked)">
<span class="feature-toggle-slider"></span>
</label>
</div>
<div class="feature-description">${feature.description}</div>
</div>
`;
});
featuresContent.innerHTML = html;
}
// Load active features from storage
function loadActiveFeatures() {
if (!window.simpleDataManager) {
console.warn('simpleDataManager not initialized yet');
return {};
}
const data = window.simpleDataManager.get('activeFeatures');
return data || {};
}
// Save active features to storage
function saveActiveFeatures(features) {
if (!window.simpleDataManager) {
console.warn('simpleDataManager not initialized yet');
return;
}
window.simpleDataManager.set('activeFeatures', features);
}
// Toggle a feature on/off
function toggleFeature(featureId, enabled) {
console.log(`${enabled ? '✅' : '❌'} Feature ${featureId}: ${enabled ? 'enabled' : 'disabled'}`);
const activeFeatures = loadActiveFeatures();
activeFeatures[featureId] = enabled;
saveActiveFeatures(activeFeatures);
// Handle special feature behaviors
if (featureId === 'video') {
if (enabled) {
// Start video
startBackgroundVideo();
} else {
// Stop video
const video = document.getElementById('backgroundVideo');
if (video) {
video.pause();
video.src = '';
}
}
}
// Apply the feature change immediately
applyFeatureSettings();
}
// Apply feature settings to game
function applyFeatureSettings() {
const activeFeatures = loadActiveFeatures();
const settings = loadAcademySettings();
const featureSettingsMap = {
'video': 'enableVideo',
'webcam': 'enableWebcam',
'dual-video': 'enableDualVideo',
'tts': 'enableTTS',
'quad-video': 'enableQuadVideo',
'hypno-spiral': 'enableHypnoSpiral',
'hypno-captions': 'enableHypnoCaptions',
'dynamic-captions': 'enableDynamicCaptions',
'tts-hypno-sync': 'enableTTSHypnoSync',
'interruptions': 'enableInterruptions',
'denial-training': 'enableDenialTraining',
'popup-images': 'enablePopupImages',
'edge-counter': 'enableEdgeCounter',
'sensory-overload': 'enableSensoryOverload',
'advanced-mode': 'enableAdvancedMode'
};
// Update settings based on active features
Object.keys(featureSettingsMap).forEach(featureId => {
const settingKey = featureSettingsMap[featureId];
if (activeFeatures[featureId] !== undefined) {
settings[settingKey] = activeFeatures[featureId];
}
});
saveAcademySettings(settings);
// Apply settings to the current session
if (window.game?.gameState) {
console.log('🎮 Applying feature settings to active game session');
applyAcademySettings();
}
}
// ==========================================
// END FEATURES PANEL FUNCTIONS
// ==========================================
// 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 worship mode styling if worship-session is selected
const gameContainer = document.getElementById('gameContainer');
if (selectedTrainingMode === 'worship-session') {
gameContainer.classList.add('worship-mode');
console.log('🙏 Applied worship-mode styling');
} else {
gameContainer.classList.remove('worship-mode');
}
// Apply settings before starting
applyAcademySettings();
// Get settings for TTS and video
const settings = loadAcademySettings();
// Debug: Check video library status
console.log('🎬 Video library check:');
console.log(' - trainingVideoLibrary length:', trainingVideoLibrary?.length || 0);
console.log(' - settings.enableVideo:', settings.enableVideo);
console.log(' - window.libraryManager exists:', !!window.libraryManager);
// Ensure trainingVideoLibrary is populated
if ((!trainingVideoLibrary || trainingVideoLibrary.length === 0) && window.libraryManager) {
console.log('📹 trainingVideoLibrary empty, loading from libraryManager...');
const allVideos = window.libraryManager.getAllVideosWithMetadata();
if (allVideos && allVideos.length > 0) {
trainingVideoLibrary = allVideos;
console.log(`✅ Loaded ${trainingVideoLibrary.length} videos from libraryManager`);
}
}
// Start background video if enabled
if (settings.enableVideo && trainingVideoLibrary.length > 0) {
console.log(`🎬 Starting video playback (${trainingVideoLibrary.length} videos available)`);
startBackgroundVideo();
} else {
if (!settings.enableVideo) {
console.log('⏸️ Video disabled in settings');
} else {
console.warn('⚠️ No videos available for playback');
}
}
// 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';
// Show and initialize features panel
const featuresPanel = document.getElementById('featuresPanel');
if (featuresPanel) {
featuresPanel.style.display = 'block';
updateFeaturesPanel();
}
// Apply feature settings before starting
applyFeatureSettings();
// 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.academyScenarios) {
console.log('🎓 Training Academy mode detected - loading Academy scenarios directly');
// Check if a specific level was selected from level select screen
if (window.selectedAcademyLevel) {
console.log(`🎯 Starting specific level: ${window.selectedLevelNumber} - ${window.selectedAcademyLevel.name}`);
trainingTasks = [{
id: window.selectedAcademyLevel.id,
text: window.selectedAcademyLevel.name,
difficulty: window.selectedAcademyLevel.difficulty || 'Medium',
type: 'main',
interactiveType: window.selectedAcademyLevel.interactiveType,
interactiveData: window.selectedAcademyLevel.interactiveData,
isScenario: true,
level: window.selectedAcademyLevel.level,
arc: window.selectedAcademyLevel.arc
}];
} else {
// Convert academyScenarios object (level1, level2, etc.) to array
trainingTasks = Object.values(window.trainingGameData.academyScenarios).map(scenario => ({
id: scenario.id,
text: scenario.name,
difficulty: scenario.difficulty || 'Medium',
type: 'main',
interactiveType: scenario.interactiveType,
interactiveData: scenario.interactiveData,
isScenario: true,
level: scenario.level,
arc: scenario.arc
}));
}
console.log(`📋 Loaded ${trainingTasks.length} Academy scenario(s)`);
console.log('🔍 First scenario:', trainingTasks[0]?.text, '- 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.academyScenarios) {
// Only use Academy scenarios if they exist and the mode is specifically training-academy
fallbackData = Object.values(window.trainingGameData.academyScenarios);
console.log(`📋 Using Academy scenarios 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.name || scenario.text,
difficulty: scenario.difficulty || 'Medium',
type: 'main',
interactiveType: scenario.interactiveType,
interactiveData: scenario.interactiveData,
isScenario: true,
level: scenario.level,
arc: scenario.arc
}));
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, customContainer) {
console.log('🎯 Training Academy: Custom interactive task display called');
// If a custom container is provided (from scenario actions), use it
if (customContainer) {
console.log('🎯 Using custom container for interactive task:', task.interactiveType);
return originalDisplayInteractiveTask.call(this, task, customContainer);
}
console.log('🎯 Skipping default interactive task display');
// Skip interactive tasks in training academy that don't have custom containers
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('gameContainer');
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('gameContainer');
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 gameContainer or body
const fallbackContainer = document.getElementById('gameContainer') || 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();
// Show game stats panel in sidebar
const gameStatsPanel = document.getElementById('game-stats-panel');
console.log('📊 LEVEL START - Game stats panel element:', gameStatsPanel);
if (gameStatsPanel) {
console.log('📊 LEVEL START - Current display value:', gameStatsPanel.style.display);
gameStatsPanel.style.display = 'block';
console.log('📊 LEVEL START - Game stats panel set to display: block');
console.log('📊 LEVEL START - New display value:', gameStatsPanel.style.display);
} else {
console.error('❌ LEVEL START - Game stats panel element not found!');
}
// Load the first training task directly
if (trainingTasks.length > 0) {
console.log('📋 Loading first training task directly...');
window.game.currentTaskIndex = 0;
window.game.currentTask = trainingTasks[0];
window.game.gameState.currentTask = trainingTasks[0]; // CRITICAL: Set gameState.currentTask for scenario progression
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('gameContainer');
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();
}
// Check if we're in Academy mode with a selected level
if (selectedTrainingMode === 'training-academy' && window.selectedAcademyLevel) {
returnToLevelSelect();
} else {
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('gameContainer');
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;
}
// Initialize scenarioState for progression tracking
if (!task.scenarioState) {
task.scenarioState = {
currentStep: 'start',
stepNumber: 1,
totalSteps: Object.keys(task.interactiveData.steps).length,
choices: [],
completed: false,
outcome: null
};
console.log('🎭 Initialized scenarioState:', task.scenarioState);
}
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) => {
const safeText = choice.text.replace(/'/g, '&apos;');
const safePreview = (choice.preview || '').replace(/'/g, '&apos;');
return `
<div class="choice-option" onclick="selectScenarioChoice('${choice.nextStep}')">
<h4>${safeText}</h4>
${choice.preview ? `<p class="choice-preview">${safePreview}</p>` : ''}
</div>
`;
}).join('')}
</div>
<div class="training-controls">
<button onclick="showQuitConfirmation()" class="next-btn">Quit Training</button>
</div>
</div>
`;
} else if (step.type === 'text' || step.type === 'story') {
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') {
// Check if this action uses the interactive task manager
// Support both interactiveType and actionType
const taskType = step.interactiveType || step.actionType;
if (taskType) {
console.log(`🎮 Action step with interactive type: ${taskType}`);
// Check if this is an edge task
const isEdgeTask = taskType === 'edge' || taskType === 'edge-session';
// Create interactive task and display it
const interactiveTask = {
id: `${scenario.id}-${stepId}`,
text: step.story || 'Complete this action',
interactiveType: taskType,
params: step.params || {
// Pass through relevant step properties as params
duration: step.duration,
preferredTags: step.preferredTags,
edgeCount: step.edgeCount,
requirePhoto: step.requirePhoto,
continuousEdging: step.continuousEdging,
requireKneeling: step.requireKneeling,
ttsText: step.ttsText
},
nextStep: step.nextStep
};
// Store next step for later
window.currentScenarioNextStep = step.nextStep;
window.currentScenarioNextStep = step.nextStep;
// Display using interactive task manager
if (window.game && window.game.interactiveTaskManager) {
// For edge tasks, check if we should preserve existing interactive content
const existingInteractiveContainer = document.getElementById('interactive-task-container');
const hasExistingInteractive = existingInteractiveContainer && existingInteractiveContainer.children.length > 0;
if (isEdgeTask && hasExistingInteractive) {
// Preserve existing content, just add edge UI to it
console.log('🎯 Edge task detected - preserving existing interactive content');
// Update story text without replacing container
const storyDiv = container.querySelector('.scenario-story');
if (storyDiv) {
storyDiv.innerHTML = step.story;
}
const taskContainer = existingInteractiveContainer;
const taskType = window.game.interactiveTaskManager.taskTypes.get(interactiveTask.interactiveType);
if (taskType && taskType.handler) {
console.log(`🎮 Calling handler for: ${interactiveTask.interactiveType} (preserving content)`);
taskType.handler.call(window.game.interactiveTaskManager, interactiveTask, taskContainer);
}
// Update the complete button
const completeBtn = document.getElementById('interactive-complete-btn');
if (completeBtn) {
completeBtn.disabled = true;
completeBtn.textContent = 'Continue to Next Step';
// Remove old listeners by cloning
const newBtn = completeBtn.cloneNode(true);
completeBtn.parentNode.replaceChild(newBtn, completeBtn);
newBtn.addEventListener('click', () => {
console.log('🎯 Interactive complete button clicked, advancing to:', step.nextStep);
completeScenarioAction(step.nextStep);
});
}
} else {
// Create new wrapper container (default behavior)
container.innerHTML = `
<div class="training-task scenario-action-interactive">
<h3>🎭 ${scenario.title}</h3>
<div class="scenario-story">
${step.story}
</div>
<div id="interactive-task-container" class="interactive-container"></div>
<div class="training-controls">
<button id="interactive-complete-btn" class="complete-btn" disabled>
Continue to Next Step
</button>
<button onclick="showQuitConfirmation()" class="next-btn">Quit Training</button>
</div>
</div>
`;
const taskContainer = document.getElementById('interactive-task-container');
// Display the interactive task by calling the handler directly
setTimeout(() => {
const taskType = window.game.interactiveTaskManager.taskTypes.get(interactiveTask.interactiveType);
if (taskType && taskType.handler) {
console.log(`🎮 Calling handler for: ${interactiveTask.interactiveType}`);
// Handler signature is: handler(task, container)
taskType.handler.call(window.game.interactiveTaskManager, interactiveTask, taskContainer);
} else {
console.error(`❌ No handler found for interactive type: ${interactiveTask.interactiveType}`);
}
// Setup complete button to advance scenario (after handler runs)
const completeBtn = document.getElementById('interactive-complete-btn');
if (completeBtn) {
completeBtn.addEventListener('click', () => {
console.log('🎯 Interactive complete button clicked, advancing to:', step.nextStep);
completeScenarioAction(step.nextStep);
});
} else {
console.error('❌ Interactive complete button not found');
}
}, 100);
}
} else {
console.error('❌ Interactive task manager not available');
stepHtml = `<div class="error">Interactive task manager not loaded</div>`;
}
return; // Skip the rest of the rendering
}
// Fallback to basic timer for non-interactive actions
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';
// Check if this is an edging/stroking task that should activate webcam
const isEdgingTask = (step.actionText && (
step.actionText.toLowerCase().includes('edging') ||
step.actionText.toLowerCase().includes('edge') ||
step.actionText.toLowerCase().includes('stroking') ||
step.actionText.toLowerCase().includes('stroke')
)) || (step.story && (
step.story.toLowerCase().includes('edging') ||
step.story.toLowerCase().includes('edge') ||
step.story.toLowerCase().includes('stroking') ||
step.story.toLowerCase().includes('stroke')
));
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>
` : ''}
${isEdgingTask ? `
<div class="webcam-mirror-hint" style="margin: 15px 0; padding: 15px; background: rgba(131, 56, 236, 0.2); border-left: 3px solid var(--color-primary); border-radius: 8px;">
<p style="margin: 0; color: var(--color-primary);">📹 <strong>Webcam activated</strong> - Watch yourself perform this task</p>
</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>
<button id="skip-action-timer-btn" class="skip-timer-btn" style="margin-top: 10px;">⏩ Skip Timer (Testing)</button>
</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);
// Activate webcam for edging tasks
console.log('🔍 Checking webcam activation:', {
isEdgingTask,
hasWebcamManager: !!window.game?.webcamManager,
hasCampaignManager: !!window.campaignManager,
webcamUnlocked: window.campaignManager?.isFeatureUnlocked('webcam')
});
if (isEdgingTask) {
// Check if webcam is in active features
const activeFeatures = loadActiveFeatures();
const webcamActive = activeFeatures['webcam'] !== false;
console.log('📹 Edging task detected, webcam active:', webcamActive);
if (window.game?.webcamManager && webcamActive) {
console.log('📹 Activating webcam for edging task');
const mirrorTask = {
mirrorInstructions: 'Watch yourself as you perform this task',
mirrorTaskText: step.actionText,
duration: duration,
taskText: step.actionText,
onComplete: () => {
console.log('📹 Webcam mirror completed');
}
};
window.game.webcamManager.startMirrorMode(mirrorTask).then(() => {
console.log('✅ Webcam mirror mode activated successfully');
}).catch(err => {
console.warn('⚠️ Webcam activation failed:', err);
});
} else {
if (!window.game?.webcamManager) {
console.warn('⚠️ Webcam manager not available');
}
if (!webcamActive) {
console.warn('⚠️ Webcam feature not active. Enable it in the Features panel.');
}
}
}
}, 100); // Small delay to ensure DOM is ready
} else if (step.type === 'completion') {
// Save Academy progress if this was a level using the campaignManager
let completionResult = null;
if (selectedTrainingMode === 'training-academy' && window.selectedLevelNumber && window.campaignManager) {
const levelNum = window.selectedLevelNumber;
completionResult = window.campaignManager.completeLevel(levelNum, {
duration: Date.now() - (window.levelStartTime || Date.now())
});
console.log(`✅ Level ${levelNum} completed:`, completionResult);
}
// Build features unlocked message
let featuresMessage = '';
if (completionResult && completionResult.unlockedFeatures && completionResult.unlockedFeatures.length > 0) {
const featureNames = {
'video': '🎬 Video Playback',
'webcam': '📹 Webcam Mirror',
'dual-video': '📺 Dual Video',
'tts': '🔊 Text-to-Speech',
'quad-video': '🖥️ Quad Video',
'hypno-spiral': '🌀 Hypno Spiral',
'hypno-captions': '💬 Hypno Captions',
'dynamic-captions': '✨ Dynamic Captions',
'tts-hypno-sync': '🎵 TTS Hypno Sync',
'interruptions': '⚡ Interruptions',
'denial-training': '🚫 Denial Training',
'popup-images': '🖼️ Popup Images',
'edge-counter': '🔢 Edge Counter',
'sensory-overload': '💥 Sensory Overload',
'advanced-mode': '🎓 Advanced Mode',
'graduation': '🎉 Graduation'
};
featuresMessage = `
<div class="features-unlocked">
<h4>🎁 New Features Unlocked!</h4>
<ul>
${completionResult.unlockedFeatures.map(f =>
`<li>${featureNames[f] || f}</li>`
).join('')}
</ul>
</div>
`;
}
stepHtml = `
<div class="training-task scenario-completion">
<h3>✅ ${scenario.title} - Complete!</h3>
<div class="scenario-story">
${step.story}
</div>
${featuresMessage}
<div class="completion-outcome">
<h4>🎯 Outcome: ${step.outcome || 'Completed'}</h4>
</div>
<div class="training-controls">
${selectedTrainingMode === 'training-academy' ?
'<button onclick="returnToLevelSelect()" class="complete-btn">Level Select</button>' :
'<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 === 'inventory-check') {
// Build inventory questionnaire HTML
console.log('📋 Building inventory questionnaire');
let questionnaireHtml = '<div class="inventory-questionnaire">';
const categories = step.inventoryCategories || {};
Object.keys(categories).forEach(categoryKey => {
const category = categories[categoryKey];
questionnaireHtml += `
<div class="inventory-category">
<h3 class="category-title">${category.title}</h3>
<div class="inventory-items">
`;
Object.keys(category.items).forEach(itemKey => {
const item = category.items[itemKey];
if (item.type === 'boolean') {
questionnaireHtml += `
<div class="inventory-item">
<label class="checkbox-label">
<input type="checkbox" class="inventory-checkbox" data-item="${itemKey}">
<span>${item.label}</span>
</label>
</div>
`;
} else {
questionnaireHtml += `
<div class="inventory-item">
<label class="select-label">
<span>${item.label}:</span>
<select class="inventory-select" data-item="${itemKey}">
${item.options.map(opt => `<option value="${opt}">${opt}</option>`).join('')}
</select>
</label>
</div>
`;
}
});
questionnaireHtml += `
</div>
</div>
`;
});
questionnaireHtml += '</div>';
stepHtml = `
<div class="training-task scenario-inventory">
<h3>📋 ${scenario.title}</h3>
<div class="scenario-story">
${step.story}
</div>
${questionnaireHtml}
<div class="training-controls">
<button onclick="submitInventory('${step.nextStep}')" class="action-btn">Submit Inventory</button>
<button onclick="showQuitConfirmation()" class="next-btn">Quit Training</button>
</div>
</div>
`;
} else if (step.type === 'path-generation') {
stepHtml = `
<div class="training-task scenario-summary">
<h3>📊 ${scenario.title}</h3>
<div class="scenario-story">
${step.story}
</div>
<div id="inventory-summary-content" style="margin: 20px 0;">
<p>Calculating your progression...</p>
</div>
<div class="training-controls" id="summary-controls" style="display: none;">
${step.choices.map((choice, index) => {
const btnClass = index === 0 ? 'action-btn' : 'next-btn';
return `<button onclick="selectScenarioChoice('${choice.nextStep}')" class="${btnClass}">${choice.text}</button>`;
}).join('')}
</div>
</div>
`;
// Generate the path after rendering
setTimeout(() => {
generateInventoryPath(step);
}, 100);
} else if (step.type === 'photo-verification') {
const photoReq = step.photoRequirements || { count: 3 };
photosNeeded = photoReq.count; // Set global photosNeeded from step data
stepHtml = `
<div class="training-task scenario-photo">
<h3>📸 ${scenario.title}</h3>
<div class="scenario-story">
${step.story}
</div>
${photoReq.items && photoReq.items.length > 0 ? `
<div class="photo-requirements" style="margin: 20px 0; padding: 15px; background: rgba(255,255,255,0.05); border-radius: 10px;">
<h4>👗 Required Items:</h4>
<ul style="list-style: none; padding-left: 0;">
${photoReq.items.map(item => `<li>✓ ${item}</li>`).join('')}
</ul>
${photoReq.edging ? '<p style="color: #ff69b4; font-weight: bold;">⚡ Edge during this photo session</p>' : ''}
</div>
` : ''}
<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">${photoReq.count}</span></p>
<button onclick="completePhotoSession('${step.nextStep}')" 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="training-controls">
<button id="complete-photo-btn" onclick="selectScenarioChoice('${step.nextStep}')" class="complete-btn" disabled>
Continue (<span id="photo-count-display">0/${photoReq.count}</span>)
</button>
<button onclick="showQuitConfirmation()" class="next-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="returnToModeSelection()" class="complete-btn">Back to Start</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' || step.type === 'story') {
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;
// Update scenarioState if it exists
if (currentScenarioTask && currentScenarioTask.scenarioState) {
currentScenarioTask.scenarioState.currentStep = nextStep;
currentScenarioTask.scenarioState.stepNumber++;
console.log('🎯 Updated scenarioState to step:', nextStep);
}
// Find the appropriate container
const gameContainer = document.getElementById('gameContainer');
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 submitInventory(nextStep) {
console.log('📦 Submitting inventory');
// Initialize categorized inventory structure
const inventory = {
clothing: {},
accessories: {},
toys: {},
environment: {}
};
// Get step definition to access category mapping
const step = currentScenarioTask.interactiveData.steps[currentScenarioStep];
// Build category map (itemKey -> categoryKey)
const categoryMap = {};
Object.keys(step.inventoryCategories).forEach(categoryKey => {
Object.keys(step.inventoryCategories[categoryKey].items).forEach(itemKey => {
categoryMap[itemKey] = categoryKey;
});
});
// Collect select dropdowns and categorize
document.querySelectorAll('.inventory-select').forEach(select => {
const itemKey = select.dataset.item;
const category = categoryMap[itemKey];
if (category) {
inventory[category][itemKey] = select.value;
}
});
// Collect checkboxes and categorize
document.querySelectorAll('.inventory-checkbox').forEach(checkbox => {
const itemKey = checkbox.dataset.item;
const category = categoryMap[itemKey];
if (category) {
inventory[category][itemKey] = checkbox.checked;
}
});
// Store in scenario state
if (currentScenarioTask && currentScenarioTask.scenarioState) {
currentScenarioTask.scenarioState.inventory = inventory;
}
console.log('📦 Collected categorized inventory:', inventory);
// Move to next step
selectScenarioChoice(nextStep);
}
function generateInventoryPath(step) {
console.log('🎯 Generating inventory path');
if (!window.InventoryManager) {
console.error('❌ InventoryManager not loaded!');
document.getElementById('inventory-summary-content').innerHTML = '<p style="color: red;">Error: Inventory system not loaded</p>';
return;
}
const inventory = currentScenarioTask.scenarioState.inventory;
const inventoryManager = new window.InventoryManager();
inventoryManager.setInventory(inventory);
const tier = inventoryManager.tier;
const summary = inventoryManager.getInventorySummary();
const photoCount = inventoryManager.getPhotoCountForTier();
console.log(`📊 Tier ${tier}: ${summary.totalItems} items, ${photoCount} photos`);
// Store in scenario state
currentScenarioTask.scenarioState.tier = tier;
currentScenarioTask.scenarioState.photoCount = photoCount;
currentScenarioTask.scenarioState.inventoryManager = inventoryManager;
// Generate progression
const progression = inventoryManager.generatePhotoProgression();
console.log(`📸 Generated ${progression.length} photo challenges`);
// Add challenges to scenario steps
const scenario = currentScenarioTask.interactiveData;
progression.forEach((challenge, index) => {
const stepId = `challenge_${index + 1}`;
const isLastChallenge = index === progression.length - 1;
const nextStepId = isLastChallenge ? `tier_${tier}_ending` : `challenge_${index + 2}`;
scenario.steps[stepId] = {
type: 'photo-verification',
mood: 'progressive',
story: challenge.instruction,
photoRequirements: {
items: challenge.requiredItems,
pose: challenge.pose,
edging: challenge.edging,
count: 1 // Each challenge is 1 photo in the progression
},
nextStep: nextStepId
};
});
console.log(`✅ Added ${progression.length} challenge steps`);
// Display summary
const summaryHtml = `
<div class="inventory-summary">
<div class="tier-badge tier-${tier}">Tier ${tier}</div>
<div class="summary-stats">
<div class="stat">
<span class="stat-label">Items Available:</span>
<span class="stat-value">${summary.totalItems}</span>
</div>
<div class="stat">
<span class="stat-label">Photos Required:</span>
<span class="stat-value">${photoCount}</span>
</div>
</div>
<div class="inventory-lists">
${summary.itemsByCategory.clothing.length > 0 ? `
<div class="item-category">
<h4>👗 Clothing</h4>
<ul>${summary.itemsByCategory.clothing.map(item => `<li>${item}</li>`).join('')}</ul>
</div>
` : ''}
${summary.itemsByCategory.accessories.length > 0 ? `
<div class="item-category">
<h4>💄 Accessories</h4>
<ul>${summary.itemsByCategory.accessories.map(item => `<li>${item}</li>`).join('')}</ul>
</div>
` : ''}
${summary.itemsByCategory.toys.length > 0 ? `
<div class="item-category">
<h4>🔞 Toys</h4>
<ul>${summary.itemsByCategory.toys.map(item => `<li>${item}</li>`).join('')}</ul>
</div>
` : ''}
${summary.itemsByCategory.environment.length > 0 ? `
<div class="item-category">
<h4>📸 Environment</h4>
<ul>${summary.itemsByCategory.environment.map(item => `<li>${item}</li>`).join('')}</ul>
</div>
` : ''}
</div>
</div>
`;
document.getElementById('inventory-summary-content').innerHTML = summaryHtml;
document.getElementById('summary-controls').style.display = 'block';
}
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;
// Update scenarioState if it exists
if (currentScenarioTask && currentScenarioTask.scenarioState) {
currentScenarioTask.scenarioState.currentStep = nextStep;
currentScenarioTask.scenarioState.stepNumber++;
console.log('⏭️ Updated scenarioState to step:', nextStep);
}
// Find the appropriate container and continue scenario
const gameContainer = document.getElementById('gameContainer');
const fallbackContainer = document.getElementById('training-task-display');
const container = gameContainer || fallbackContainer;
if (container) {
displayScenarioStep(container, nextStep);
}
window.trainingAcademyProcessing = false;
}, 100);
}
// Expose to window for interactiveTaskManager to call
window.proceedToNextStep = proceedToNextStep;
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('gameContainer');
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('gameContainer') || 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');
const skipBtn = document.getElementById('skip-action-timer-btn');
console.log('⏱️ Starting action timer:', duration, 'seconds, forced:', forceComplete);
// Skip button handler
if (skipBtn) {
skipBtn.addEventListener('click', function() {
console.log('⏩ Skip button clicked - completing action timer');
timeLeft = 0;
});
}
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);
// Clean up any active webcam streams
const webcamVideo = document.getElementById('webcam-video');
if (webcamVideo && webcamVideo.srcObject) {
const stream = webcamVideo.srcObject;
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
webcamVideo.srcObject = null;
console.log('📹 Webcam stream stopped');
}
// Clean up any other video elements with active streams
const allVideos = document.querySelectorAll('video');
allVideos.forEach(video => {
if (video.srcObject) {
const stream = video.srcObject;
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
video.srcObject = null;
}
});
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('gameContainer') || 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('gameContainer') || 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 {
// gameData.js has already loaded data from localStorage
// Just verify and log it
console.log('📊 Current game data state:', {
hasAcademyProgress: !!window.gameData.academyProgress,
completedLevels: window.gameData.academyProgress?.completedLevels || [],
highestUnlocked: window.gameData.academyProgress?.highestUnlockedLevel || 1,
features: window.gameData.academyProgress?.featuresUnlocked || []
});
// Initialize Campaign Manager (will preserve existing progress)
if (typeof CampaignManager !== 'undefined' && !window.campaignManager) {
window.campaignManager = new CampaignManager();
console.log('🎯 Campaign Manager initialized');
} else if (window.campaignManager) {
console.log('🎯 Campaign Manager already available');
} else {
console.warn('⚠️ CampaignManager class not available');
}
// Initialize TTS system
await initializeTTS();
// Load TTS setting from localStorage
const savedSettings = loadAcademySettings();
ttsEnabled = savedSettings.enableTTS;
console.log(`🔊 TTS initialized: ${ttsEnabled ? 'enabled' : 'disabled'}`);
// 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);
});
// Initialize simpleDataManager globally
window.simpleDataManager = {
data: null,
storageKey: 'webGame-data',
loadData() {
try {
const stored = localStorage.getItem(this.storageKey);
if (stored) {
this.data = JSON.parse(stored);
} else {
this.data = {};
}
} catch (e) {
console.error('Error loading data:', e);
this.data = {};
}
},
saveData() {
try {
if (!this.data) this.data = {};
this.data.timestamp = Date.now();
localStorage.setItem(this.storageKey, JSON.stringify(this.data));
} catch (e) {
console.error('Error saving data:', e);
}
},
get(key) {
if (!this.data) this.loadData();
return this.data[key];
},
set(key, value) {
if (!this.data) this.loadData();
this.data[key] = value;
this.saveData();
}
};
// Start initialization when DOM is loaded
document.addEventListener('DOMContentLoaded', async () => {
console.log('🎓 Training Academy DOM loaded');
// Initialize BackupManager for emergency cleanup
if (typeof BackupManager !== 'undefined') {
window.backupManager = new BackupManager();
console.log('🛡️ Backup Manager initialized for training academy');
}
// Initialize desktop file manager if in Electron environment
if (window.electronAPI && typeof DesktopFileManager !== 'undefined') {
// Use the global simpleDataManager
// Load data initially
window.simpleDataManager.loadData();
window.desktopFileManager = new DesktopFileManager(window.simpleDataManager);
await window.desktopFileManager.init();
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) {
try {
// Call original first
originalSetItem(key, value);
} catch (error) {
if (error.name === 'QuotaExceededError') {
console.error('🚨 LocalStorage quota exceeded! Performing emergency cleanup...');
if (window.backupManager) {
window.backupManager.performEmergencyCleanup();
}
// Try again after cleanup
try {
originalSetItem(key, value);
console.log('✅ Save succeeded after cleanup');
} catch (retryError) {
console.error('❌ Save failed even after cleanup:', retryError);
// Don't show alert for every failed save
}
} else {
throw error;
}
}
// 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(event.detail.photos);
// Reset session state
photoSessionActive = false;
}
});
/**
* Handle photo session completion - called when WebcamManager ends session
*/
async function handlePhotoSessionCompletion(photos) {
console.log(`📸 [HANDLER] Photo session completion: ${photos.length} photos`);
console.log('📊 [HANDLER] photosNeeded:', photosNeeded);
console.log('📊 [HANDLER] photoSessionActive:', photoSessionActive);
// Clear the photo progress monitoring interval
if (window.photoProgressInterval) {
console.log('🔄 [HANDLER] Clearing photo progress interval');
clearInterval(window.photoProgressInterval);
window.photoProgressInterval = null;
}
// Update photo progress display
console.log('🔄 [HANDLER] Updating photo progress display');
updatePhotoProgress();
// Add captured photos to library
if (photos.length > 0) {
console.log('📚 [HANDLER] Adding photos to library');
await addCapturedPhotosToLibrary(photos);
}
// Enable the continue button
const completeBtn = document.getElementById('complete-photo-btn');
console.log('🔍 [HANDLER] Complete button:', completeBtn);
if (completeBtn) {
completeBtn.disabled = false;
const countDisplay = document.getElementById('photo-count-display');
if (countDisplay) {
countDisplay.textContent = `${photos.length}/${photosNeeded}`;
}
console.log('✅ [HANDLER] Continue button enabled');
}
// Get the next step from the current scenario step
const step = currentScenarioTask?.interactiveData?.steps?.[currentScenarioStep];
const nextStep = step?.nextStep;
console.log('🔍 [HANDLER] Next step:', nextStep);
console.log('🔍 [HANDLER] Photos vs needed:', photos.length, '>=', photosNeeded, '?', photos.length >= photosNeeded);
// Auto-advance if we have enough photos
if (nextStep && photos.length >= photosNeeded) {
console.log('🚀 [HANDLER] Auto-advancing to:', nextStep, 'in 1.5 seconds');
setTimeout(() => {
console.log('⏰ [HANDLER] Timeout fired - calling selectScenarioChoice');
selectScenarioChoice(nextStep);
}, 1500);
} else {
console.log('⚠️ [HANDLER] Not auto-advancing - nextStep:', nextStep, 'photos:', photos.length, 'needed:', photosNeeded);
}
}
// 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 WITH PROGRESS TRACKING
const sessionData = {
requirements: {
count: photosNeeded,
description: 'Take photos during this feminization session'
}
};
const success = await currentWebcamManager.startPhotoSessionWithProgress('dress-up-session', sessionData);
if (!success) {
throw new Error('Failed to start webcam session');
}
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(nextStep) {
// This function is called by the "End Session" button
// to manually end the photo session before required photos are captured
if (!photoSessionActive) {
console.log('📸 Photo session not active');
return;
}
console.log('📸 Manually ending photo session');
// End the webcam session - this will trigger photoSessionComplete event
if (currentWebcamManager && currentWebcamManager.endPhotoSession) {
await currentWebcamManager.endPhotoSession();
}
// The photoSessionComplete event listener will handle the rest
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');
const sidebarXpElement = document.getElementById('sidebar-current-xp');
if (xpElement || sidebarXpElement) {
const currentXP = getCurrentScenarioXP();
if (xpElement) xpElement.textContent = currentXP;
if (sidebarXpElement) {
sidebarXpElement.textContent = currentXP;
const statsPanel = document.getElementById('game-stats-panel');
console.log('📊 XP UPDATE - Sidebar XP updated to:', currentXP, '| Stats panel display:', statsPanel?.style.display);
}
}
// 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>