training-academy/training-academy.html

6290 lines
286 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gooner Training Academy</title>
<!-- Core Styles -->
<link rel="stylesheet" href="src/styles/color-variables.css">
<link rel="stylesheet" href="src/styles/styles.css">
<link rel="stylesheet" href="src/styles/styles-dark-edgy.css">
<link rel="stylesheet" href="src/styles/base-video-player.css">
<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;
}
.academy-nav .nav-center {
flex: 1;
text-align: center;
}
.academy-subtitle {
color: var(--header-subtitle-color);
font-size: 1rem;
font-style: italic;
opacity: 0.9;
}
.academy-nav .nav-right {
display: flex;
gap: 0.5rem;
align-items: center;
}
/* Button Styles */
.btn {
background: var(--btn-secondary-bg);
border: 1px solid var(--btn-secondary-border);
color: var(--btn-secondary-text);
border-radius: 8px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s ease;
font-family: inherit;
}
.btn:hover {
background: var(--btn-secondary-hover-bg);
border-color: var(--btn-secondary-hover-border);
transform: translateY(-1px);
}
.btn-secondary {
background: var(--bg-secondary-overlay-20);
border-color: var(--color-primary);
}
.btn-secondary:hover {
background: var(--bg-secondary-overlay-30);
border-color: var(--color-primary-light);
}
.btn-warning {
background: var(--btn-danger-bg);
border-color: var(--btn-danger-border);
}
.btn-warning:hover {
background: var(--btn-danger-hover-bg);
border-color: var(--btn-danger-hover-border);
}
.academy-controls .btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.6rem 1rem;
white-space: nowrap;
}
.academy-controls .btn-icon {
font-size: 1.1rem;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.academy-nav {
flex-direction: column;
gap: 1rem;
padding: 1rem;
}
.academy-subtitle {
display: none;
}
.academy-controls {
flex-wrap: wrap;
justify-content: center;
}
.academy-controls .btn-text {
display: none;
}
.academy-controls .btn {
padding: 0.6rem;
}
}
/* Scenario Status Bar */
.scenario-status-bar {
display: flex;
flex-direction: column;
gap: 1rem;
background: var(--bg-secondary-overlay-10);
border: 1px solid var(--border-secondary);
border-radius: 10px;
padding: 1rem;
backdrop-filter: blur(5px);
position: fixed;
top: 100px;
left: 20px;
z-index: 10000;
min-width: 200px;
}
.scenario-status-bar .status-item {
display: flex;
flex-direction: column;
align-items: flex-start;
text-align: left;
padding: 0.5rem;
background: rgba(0, 0, 0, 0.3);
border-radius: 8px;
}
.scenario-status-bar .status-label {
color: var(--color-secondary);
font-size: 0.85rem;
font-weight: bold;
margin-bottom: 0.2rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.scenario-status-bar .status-value {
color: #ffffff;
font-size: 1.1rem;
font-weight: bold;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
}
.scenario-status-bar .timer-display {
font-family: 'Courier New', monospace;
color: var(--color-accent-green);
text-shadow: 0 0 10px var(--bg-primary-overlay-20);
}
.training-controls {
background: var(--bg-secondary-overlay-10);
border: 1px solid var(--border-secondary);
border-radius: 10px;
padding: 1.5rem;
margin: 1rem;
}
.library-status {
background: var(--bg-primary-overlay-10);
border: 1px solid var(--color-accent-blue);
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
font-family: 'Courier New', monospace;
}
.library-status h3 {
color: var(--color-accent-blue);
margin: 0 0 0.5rem 0;
}
.academy-mode-selection {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 1rem;
margin: 1rem auto;
max-width: 1000px;
}
.training-mode-card {
background: var(--gradient-dark);
border: 2px solid var(--color-primary);
border-radius: 8px;
padding: 0.8rem;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
margin: 0.5rem;
width: 200px;
flex-shrink: 0;
}
.training-mode-card:hover {
border-color: var(--card-hover-border);
box-shadow: var(--shadow-glow-primary);
transform: translateY(-1px);
}
.training-mode-card.selected {
border-color: var(--card-hover-border);
background: var(--gradient-accent);
box-shadow: var(--shadow-glow-primary);
}
.mode-icon {
font-size: 1.8rem;
margin-bottom: 0.4rem;
}
.mode-name {
font-size: 1rem;
font-weight: bold;
color: var(--text-primary);
margin-bottom: 0.2rem;
}
.mode-description {
color: var(--text-muted);
font-size: 0.75rem;
line-height: 1.2;
}
.academy-start-controls {
text-align: center;
margin: 1rem;
}
.start-training-btn {
background: var(--gradient-accent);
color: white;
border: none;
border-radius: 25px;
padding: 1rem 2rem;
font-size: 1.2rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: var(--shadow-glow-primary);
}
.start-training-btn:hover:not(:disabled) {
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-secondary-dark));
box-shadow: 0 6px 20px var(--bg-primary-overlay-20);
transform: translateY(-2px);
}
.start-training-btn:disabled {
background: var(--text-dim);
cursor: not-allowed;
box-shadow: none;
}
.back-navigation {
position: fixed;
top: 20px;
left: 20px;
z-index: 1000;
}
.back-btn {
background: var(--bg-secondary-overlay-90);
color: var(--text-primary);
border: 1px solid var(--border-primary);
border-radius: 8px;
padding: 0.5rem 1rem;
text-decoration: none;
font-weight: bold;
transition: all 0.3s ease;
}
.back-btn:hover {
background: var(--bg-secondary);
border-color: var(--color-primary);
}
/* Mirror Task Action Buttons */
.action-btn {
background: linear-gradient(135deg, #27ae60, #2ecc71);
color: white;
border: none;
border-radius: 8px;
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
margin: 0.5rem;
}
.action-btn:hover {
background: linear-gradient(135deg, #219a52, #27ae60);
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
transform: translateY(-2px);
}
.skip-btn {
background: linear-gradient(135deg, #95a5a6, #bdc3c7);
color: var(--text-dark);
border: none;
border-radius: 8px;
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(149, 165, 166, 0.3);
margin: 0.5rem;
}
.skip-btn:hover {
background: linear-gradient(135deg, #7f8c8d, #95a5a6);
box-shadow: 0 6px 20px rgba(149, 165, 166, 0.4);
transform: translateY(-2px);
}
.next-btn {
background: linear-gradient(135deg, #3498db, #5dade2);
color: white;
border: none;
border-radius: 8px;
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
margin: 0.5rem;
}
.next-btn:hover {
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
transform: translateY(-2px);
}
/* Video player container for background videos */
.training-video-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background: var(--bg-black);
overflow: hidden;
}
.training-video-container video {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.3;
}
/* Ensure main content is above video */
.main-content {
position: relative;
z-index: 1;
background: rgba(0, 0, 0, 0.7);
min-height: 100vh;
padding-top: 80px; /* Space for fixed header */
}
/* Video controls overlay */
.video-controls-overlay {
position: fixed;
top: 200px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
border-radius: 10px;
padding: 10px;
z-index: 100;
display: none; /* Hidden until video loads */
}
.video-controls-overlay button {
background: #3498db;
color: white;
border: none;
border-radius: 5px;
padding: 5px 10px;
margin: 2px;
cursor: pointer;
font-size: 0.8rem;
}
.video-controls-overlay button:hover {
background: #2980b9;
}
/* Training Academy Specific Styles */
.training-status, .training-task {
background: var(--card-bg);
border: 2px solid var(--card-border);
border-radius: 15px;
padding: 25px;
margin: 20px;
text-align: center;
backdrop-filter: blur(10px);
}
.training-task {
text-align: left;
max-width: 800px;
margin: 20px auto;
}
.training-task h3 {
color: var(--color-primary);
margin-bottom: 15px;
text-align: center;
}
.task-content {
background: var(--bg-overlay-light);
padding: 20px;
border-radius: 10px;
margin: 20px 0;
border-left: 4px solid var(--color-secondary);
font-size: 1.1em;
line-height: 1.5;
}
.task-story {
background: rgba(58, 134, 255, 0.2);
padding: 15px;
border-radius: 10px;
margin: 15px 0;
border-left: 4px solid var(--color-accent);
font-size: 1em;
line-height: 1.4;
}
.task-story p {
margin: 0;
color: var(--text-secondary);
}
.training-controls {
display: flex;
gap: 15px;
justify-content: center;
margin-top: 25px;
}
.complete-btn, .next-btn {
background: var(--gradient-accent);
color: white;
border: none;
padding: 12px 25px;
border-radius: 25px;
cursor: pointer;
font-weight: bold;
font-size: 1em;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
}
.complete-btn:hover, .next-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-glow-primary);
}
.next-btn {
background: linear-gradient(45deg, #8338ec, #3a86ff);
}
.next-btn:hover {
box-shadow: 0 8px 20px rgba(131, 56, 236, 0.4);
}
.interactive-notice {
background: linear-gradient(45deg, rgba(131, 56, 236, 0.2), rgba(58, 134, 255, 0.2));
border: 1px solid var(--color-secondary);
border-radius: 10px;
padding: 15px;
margin: 15px 0;
text-align: center;
}
.interactive-notice p {
margin: 0;
color: var(--color-secondary);
font-weight: bold;
}
/* Scenario Adventure Styles */
.scenario-choice, .scenario-action, .scenario-ending, .scenario-action-progress {
background: rgba(20, 20, 20, 0.95);
border: 2px solid var(--color-danger);
border-radius: 15px;
padding: 25px;
margin: 20px auto;
max-width: 900px;
backdrop-filter: blur(10px);
}
/* Training task display container */
#training-task-display {
scrollbar-width: thin;
scrollbar-color: var(--color-primary) rgba(0, 0, 0, 0.3);
}
#training-task-display::-webkit-scrollbar {
width: 8px;
}
#training-task-display::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.3);
border-radius: 4px;
}
#training-task-display::-webkit-scrollbar-thumb {
background: var(--color-primary);
border-radius: 4px;
}
#training-task-display::-webkit-scrollbar-thumb:hover {
background: var(--color-primary-light);
}
.scenario-story {
background: rgba(0, 0, 0, 0.8);
padding: 20px;
border-radius: 10px;
margin: 20px 0;
border-left: 4px solid var(--color-danger);
font-size: 1.1em;
line-height: 1.6;
color: var(--text-secondary);
}
.scenario-choices {
display: grid;
gap: 15px;
margin: 25px 0;
}
.choice-option {
background: linear-gradient(135deg, rgba(131, 56, 236, 0.4), rgba(58, 134, 255, 0.3));
border: 2px solid var(--color-secondary);
border-radius: 12px;
padding: 25px;
cursor: pointer;
transition: all 0.3s ease;
margin: 10px 0;
}
.choice-option:hover {
background: linear-gradient(135deg, rgba(131, 56, 236, 0.7), rgba(58, 134, 255, 0.5));
border-color: var(--color-accent);
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(131, 56, 236, 0.4);
}
.choice-option h4 {
color: var(--color-danger);
margin: 0 0 12px 0;
font-size: 1.3em;
font-weight: bold;
}
.choice-preview {
color: var(--text-muted);
margin: 0;
font-style: italic;
font-size: 1em;
line-height: 1.4;
}
.action-details {
background: rgba(58, 134, 255, 0.2);
border: 1px solid var(--color-accent);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
.action-text {
color: var(--color-accent);
font-weight: bold;
font-size: 1.1em;
margin: 10px 0;
}
.duration-text {
color: var(--color-warning);
font-weight: bold;
margin: 10px 0;
}
.action-timer {
background: rgba(0, 0, 0, 0.7);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
text-align: center;
}
.progress-bar {
width: 100%;
height: 20px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
overflow: hidden;
margin: 15px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #ff006e, #8338ec);
width: 0%;
transition: width 1s ease;
}
.ending-details {
background: linear-gradient(135deg, rgba(255, 215, 0, 0.2), rgba(255, 140, 0, 0.2));
border: 2px solid var(--color-gold);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
text-align: center;
}
.ending-details h4 {
color: var(--color-gold);
margin: 0 0 15px 0;
font-size: 1.3em;
}
/* Photo Session Styles */
.photo-section {
background: linear-gradient(135deg, rgba(138, 43, 226, 0.2), rgba(255, 20, 147, 0.2));
border: 2px solid var(--color-purple);
border-radius: 15px;
padding: 20px;
margin: 20px 0;
}
.photo-capture-controls {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
padding: 15px;
}
.photo-capture-controls button {
background: linear-gradient(135deg, #ff1493, #8a2be2);
border: none;
color: white;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 1em;
font-weight: bold;
transition: all 0.3s ease;
}
.photo-capture-controls button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 20, 147, 0.4);
}
#photo-progress {
color: var(--color-pink);
font-weight: bold;
margin-top: 15px;
}
#webcam-container {
border-radius: 10px;
overflow: hidden;
max-width: 100%;
margin: 0 auto;
}
.outcome-text {
color: var(--color-orange);
font-weight: bold;
margin: 0;
}
/* Focus Task Styles */
.focus-task-simple, .focus-session-active, .focus-completed {
background: rgba(20, 20, 20, 0.95);
border: 2px solid var(--color-purple);
border-radius: 15px;
padding: 25px;
margin: 20px auto;
max-width: 800px;
text-align: center;
backdrop-filter: blur(10px);
}
.focus-timer-large {
font-size: 72px;
font-weight: bold;
color: var(--color-purple);
margin: 30px 0;
text-shadow: 0 0 20px rgba(103, 58, 183, 0.5);
}
.focus-instructions {
background: rgba(103, 58, 183, 0.2);
border: 1px solid var(--color-purple);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
.focus-instructions p {
margin: 8px 0;
color: var(--text-secondary);
}
.focus-status {
margin: 20px 0;
}
#focus-status-text {
font-size: 1.1em;
font-weight: bold;
color: var(--color-purple);
transition: color 0.3s ease;
}
.completion-message {
background: linear-gradient(135deg, rgba(76, 175, 80, 0.3), rgba(139, 195, 74, 0.2));
border: 2px solid var(--color-success);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
.completion-message p {
margin: 10px 0;
color: var(--color-success-light);
}
/* Video Control Panel Styles */
.control-sidebar {
position: fixed;
top: 100px;
right: 20px;
width: 220px;
display: flex;
flex-direction: column;
background: rgba(0, 0, 0, 0.8);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 10px;
padding: 15px;
gap: 10px;
height: fit-content;
z-index: 1000;
}
.video-control-panel {
background: rgba(0, 0, 0, 0.9);
border: 1px solid rgba(139, 92, 246, 0.4);
border-radius: 8px;
margin-bottom: 15px;
overflow: hidden;
}
.video-control-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: rgba(139, 92, 246, 0.2);
cursor: pointer;
transition: background 0.3s ease;
}
.video-control-header:hover {
background: rgba(139, 92, 246, 0.3);
}
.video-control-icon {
font-size: 1.2rem;
}
.video-control-title {
font-weight: 600;
color: var(--text-primary);
flex: 1;
text-align: center;
}
.video-control-toggle {
font-size: 0.9rem;
transition: transform 0.3s ease;
}
.video-control-toggle.collapsed {
transform: rotate(-90deg);
}
.video-control-content {
padding: 12px;
display: block;
transition: all 0.3s ease;
max-height: 400px;
overflow: hidden;
}
.video-control-content.collapsed {
display: none;
max-height: 0;
padding: 0 12px;
}
.control-group {
margin-bottom: 12px;
}
.control-group:last-child {
margin-bottom: 0;
}
.control-label {
display: block;
font-size: 0.8rem;
color: var(--text-muted);
margin-bottom: 5px;
font-weight: 500;
}
.control-row {
display: flex;
gap: 6px;
align-items: center;
justify-content: space-between;
}
.control-btn {
background: rgba(139, 92, 246, 0.2);
color: var(--text-primary);
border: 1px solid rgba(139, 92, 246, 0.4);
border-radius: 6px;
padding: 6px 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.8rem;
flex: 1;
min-width: 0;
text-align: center;
}
.control-btn:hover {
background: rgba(139, 92, 246, 0.4);
border-color: rgba(139, 92, 246, 0.6);
}
.volume-control {
display: flex;
align-items: center;
gap: 8px;
}
.volume-slider {
flex: 1;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
outline: none;
-webkit-appearance: none;
appearance: none;
}
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
background: var(--color-secondary);
border-radius: 50%;
cursor: pointer;
}
.volume-display {
font-size: 0.8rem;
color: var(--text-muted);
min-width: 35px;
text-align: right;
}
.playlist-select {
width: 100%;
background: rgba(0, 0, 0, 0.6);
color: var(--text-primary);
border: 1px solid rgba(139, 92, 246, 0.4);
border-radius: 6px;
padding: 6px 8px;
font-size: 0.8rem;
outline: none;
cursor: pointer;
}
.playlist-select:hover {
border-color: rgba(139, 92, 246, 0.6);
}
.playlist-select:focus {
border-color: var(--color-secondary);
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.2);
}
.video-info {
background: rgba(0, 0, 0, 0.6);
border-radius: 6px;
padding: 8px;
}
.current-video-name {
font-size: 0.8rem;
color: var(--text-primary);
margin-bottom: 6px;
text-align: center;
font-weight: 500;
}
.video-progress {
display: flex;
flex-direction: column;
gap: 4px;
}
.progress-bar {
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #8B5CF6, #EC4899);
width: 0%;
transition: width 0.1s ease;
}
.video-time {
font-size: 0.7rem;
color: var(--text-muted);
text-align: center;
}
/* Academy Setup Screen Styles */
.academy-setup {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.setup-container {
background: var(--bg-overlay-medium);
border: 2px solid var(--color-primary);
border-radius: 15px;
padding: 30px;
margin: 20px 0;
}
.setup-header {
text-align: center;
margin-bottom: 30px;
}
.setup-header h2 {
color: var(--color-primary);
font-family: 'Audiowide', sans-serif;
font-size: 28px;
margin: 0 0 10px 0;
text-shadow: var(--shadow-glow-primary);
}
.setup-header p {
color: var(--text-muted);
font-size: 16px;
margin: 0;
}
.setup-instructions {
margin-top: 25px;
}
.instruction-box {
background: linear-gradient(135deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.4)), var(--bg-primary-overlay-20);
border: 1px solid var(--border-primary);
border-radius: 12px;
padding: 20px;
text-align: left;
}
.setup-content {
margin-top: 30px;
}
.setting-group {
background: var(--bg-primary-overlay-10);
border: 1px solid var(--border-primary);
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
}
.setting-group h3 {
color: var(--color-primary);
font-family: 'Electrolize', sans-serif;
font-size: 18px;
margin: 0 0 15px 0;
text-shadow: var(--shadow-glow-primary);
}
.setting-description {
margin-bottom: 15px;
padding: 12px;
background: var(--bg-primary-overlay-10);
border-left: 3px solid var(--color-primary);
border-radius: 5px;
}
.video-settings,
.tts-settings,
.message-settings,
.popup-settings {
padding: 10px 0;
}
.video-option {
display: flex;
align-items: center;
padding: 12px;
background: var(--bg-primary-overlay-10);
border-radius: 8px;
border: 1px solid var(--border-primary);
transition: all 0.3s ease;
}
.video-option:hover {
background: var(--bg-primary-overlay-20);
border-color: var(--color-primary);
}
.video-option input[type="checkbox"] {
width: 20px;
height: 20px;
margin-right: 12px;
cursor: pointer;
accent-color: var(--color-primary);
}
.video-option label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
flex: 1;
font-family: 'Electrolize', sans-serif;
color: var(--text-primary);
}
.option-icon {
font-size: 20px;
}
.option-text {
font-size: 15px;
}
.video-options,
.tts-options {
margin-top: 15px;
padding: 15px;
background: rgba(0, 0, 0, 0.3);
border-radius: 8px;
border: 1px solid var(--border-primary);
}
.setting-row {
margin-bottom: 15px;
}
.setting-row:last-child {
margin-bottom: 0;
}
.setting-row label {
display: block;
color: var(--text-primary);
font-family: 'Electrolize', sans-serif;
font-size: 14px;
margin-bottom: 8px;
}
.slider-container {
display: flex;
align-items: center;
gap: 10px;
}
.slider {
flex: 1;
height: 6px;
border-radius: 3px;
background: var(--bg-primary-overlay-20);
outline: none;
-webkit-appearance: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--color-primary);
cursor: pointer;
box-shadow: var(--shadow-glow-primary);
}
.slider::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--color-primary);
cursor: pointer;
border: none;
box-shadow: var(--shadow-glow-primary);
}
/* Floating Start Button */
.floating-start-button {
position: fixed;
bottom: 30px;
right: 30px;
z-index: 1000;
padding: 18px 35px;
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
border: none;
border-radius: 50px;
color: var(--text-white);
font-family: 'Electrolize', sans-serif;
font-size: 18px;
font-weight: bold;
cursor: pointer;
box-shadow: var(--shadow-glow-primary);
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 12px;
animation: pulse-glow 2s ease-in-out infinite;
}
.floating-start-button:hover {
transform: translateY(-3px);
box-shadow: 0 12px 35px var(--bg-primary-overlay-20);
background: linear-gradient(135deg, var(--color-primary-light) 0%, var(--color-primary) 100%);
}
.floating-start-button:active {
transform: translateY(-1px);
}
.floating-start-button .btn-icon {
font-size: 24px;
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
}
.floating-start-button .btn-text {
font-size: 18px;
letter-spacing: 0.5px;
}
.floating-start-button:disabled {
opacity: 0.5;
cursor: not-allowed;
background: var(--text-dim);
box-shadow: none;
}
@keyframes pulse-glow {
0%, 100% {
box-shadow: var(--shadow-glow-primary);
}
50% {
box-shadow: 0 8px 35px var(--bg-primary-overlay-20);
}
}
@media (max-width: 768px) {
.floating-start-button {
bottom: 20px;
right: 20px;
padding: 15px 28px;
font-size: 16px;
}
.floating-start-button .btn-icon {
font-size: 20px;
}
.floating-start-button .btn-text {
font-size: 16px;
}
}
</style>
</head>
<body>
<!-- Page Header -->
<header class="academy-header">
<div class="academy-nav">
<div class="nav-left">
<h1>🎓 Training Academy</h1>
</div>
<div class="nav-center academy-subtitle">
Master the Art of Dedicated Gooning
</div>
<div class="nav-right academy-controls">
<!-- Theme Switcher -->
<div id="theme-switcher-container"></div>
<button id="tts-toggle" onclick="toggleTTS()" class="btn btn-secondary" title="Toggle voice narration on/off">
<span class="btn-icon">🔊</span>
<span class="btn-text">Voice: ON</span>
</button>
<button id="tts-stop" onclick="stopTTS()" class="btn btn-warning" title="Stop current voice narration">
<span class="btn-icon">⏹️</span>
<span class="btn-text">Stop</span>
</button>
<button id="back-to-home" class="btn btn-secondary" onclick="window.location.href='index.html'" title="Return to main menu">
<span class="btn-icon">🏠</span>
<span class="btn-text">Home</span>
</button>
</div>
</div>
</header>
<!-- Background Video Container -->
<div class="training-video-container" id="trainingVideoContainer">
<!-- Video will be inserted here -->
</div>
<!-- Video Control Panel (Collapsible) -->
<div class="control-sidebar">
<div id="video-control-panel" class="video-control-panel">
<div class="video-control-header" onclick="toggleVideoControls()">
<span class="video-control-icon">🎬</span>
<span class="video-control-title">Video Controls</span>
<span id="video-control-toggle" class="video-control-toggle collapsed"></span>
</div>
<div id="video-control-content" class="video-control-content collapsed">
<!-- Playback Controls -->
<div class="control-group">
<div class="control-row">
<button id="video-rewind" class="control-btn" title="Rewind 10s"></button>
<button id="video-play-pause" class="control-btn" title="Play/Pause">⏸️</button>
<button id="video-forward" class="control-btn" title="Forward 10s"></button>
<button id="video-skip" class="control-btn" title="Skip Video">⏭️</button>
</div>
</div>
<!-- Volume Control -->
<div class="control-group">
<label class="control-label">🔊 Volume</label>
<div class="volume-control">
<input type="range" id="video-volume" class="volume-slider" min="0" max="100" value="50">
<span id="video-volume-display" class="volume-display">50%</span>
</div>
</div>
<!-- Playlist Selection -->
<div class="control-group">
<label class="control-label">📂 Source</label>
<select id="video-playlist-select" class="playlist-select">
<option value="random">🎲 Random Videos</option>
<option value="playlist1">📝 Playlist 1</option>
<option value="playlist2">📝 Playlist 2</option>
<option value="playlist3">📝 Playlist 3</option>
</select>
</div>
<!-- TTS Controls -->
<div class="control-group">
<label class="control-label">🔊 Voice Narration</label>
<div class="control-row">
<button id="sidebar-tts-toggle" onclick="toggleTTS()" class="control-btn" title="Toggle voice narration on/off">
🔊 ON
</button>
<button id="sidebar-tts-stop" onclick="stopTTS()" class="control-btn" title="Stop current speech">
⏹️ Stop
</button>
</div>
<div id="sidebar-tts-status" class="control-label" style="margin-top: 5px; font-size: 0.75rem; color: var(--text-muted);">
Voice ready
</div>
</div>
<!-- Video Info -->
<div class="control-group">
<div class="video-info">
<div id="current-video-name" class="current-video-name">No video loaded</div>
<div id="video-progress" class="video-progress">
<div class="progress-bar">
<div id="progress-fill" class="progress-fill"></div>
</div>
<div id="video-time" class="video-time">00:00 / 00:00</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Game Status Bar -->
<div class="scenario-status-bar" id="scenario-status-bar" style="display: none;">
<div class="status-item">
<span class="status-label">TIME:</span>
<span id="scenario-timer" class="status-value timer-display">00:00</span>
</div>
<div class="status-item">
<span class="status-label">XP:</span>
<span id="scenario-current-xp" class="status-value">0</span>
</div>
</div>
<!-- Training Academy Setup Screen -->
<div class="academy-setup" id="academy-setup">
<div class="setup-container">
<div class="setup-title">
<h2 style="text-align: center; color: var(--color-primary); font-family: 'Audiowide', sans-serif; font-size: 28px; margin: 20px 0; text-shadow: var(--shadow-glow-primary);">🎓 Training Academy Setup</h2>
<p style="text-align: center; color: var(--text-muted); font-size: 16px; margin-bottom: 30px;">Configure your training experience and select your program</p>
</div>
<!-- Floating Start Button -->
<button id="floating-academy-start-btn" class="floating-start-button" title="Begin your training session with current settings">
<span class="btn-icon">🎓</span>
<span class="btn-text">Start Training</span>
</button>
<div class="setup-content">
<!-- Training Mode Selection -->
<div class="setting-group">
<h3>🎯 Training Program</h3>
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
<strong style="color: var(--color-primary); font-size: 15px;">Select the training program that matches your goals. Each mode offers unique tasks and challenges designed to enhance your experience.</strong>
</div>
<div class="academy-mode-selection" id="trainingModeSelection">
<!-- Training modes will be populated here -->
</div>
</div>
<!-- Video Settings -->
<div class="setting-group">
<h3>🎬 Video Experience</h3>
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
<strong style="color: var(--color-primary); font-size: 15px;">Background videos enhance immersion during training. Enable or disable video playback based on your preference and system performance.</strong>
</div>
<div class="video-settings">
<div class="video-option">
<input type="checkbox" id="academy-enable-video" checked>
<label for="academy-enable-video">
<span class="option-icon">🎬</span>
<span class="option-text">Enable Background Video</span>
</label>
</div>
<div class="video-options" id="academy-video-options" style="display: block;">
<div class="setting-row">
<label for="academy-video-opacity">Video Opacity: <span id="academy-video-opacity-value">70%</span></label>
<div class="slider-container">
<input type="range" id="academy-video-opacity" min="0.1" max="1.0" step="0.05" value="0.7" class="slider">
</div>
</div>
</div>
</div>
</div>
<!-- TTS Settings -->
<div class="setting-group">
<h3>🔊 Voice Narration (TTS)</h3>
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
<strong style="color: var(--color-primary); font-size: 15px;">Text-to-speech narration provides verbal instructions and encouragement throughout your session. Customize voice settings and volume.</strong>
</div>
<div class="tts-settings">
<div class="video-option">
<input type="checkbox" id="academy-enable-tts" checked>
<label for="academy-enable-tts">
<span class="option-icon">🔊</span>
<span class="option-text">Enable TTS Narration</span>
</label>
</div>
<div class="tts-options" id="academy-tts-options" style="display: block;">
<div class="setting-row">
<label for="academy-tts-volume">TTS Volume: <span id="academy-tts-volume-value">80%</span></label>
<div class="slider-container">
<input type="range" id="academy-tts-volume" min="0" max="100" value="80" class="slider">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Level Select Screen (Hidden initially) -->
<div id="levelSelectScreen" class="level-select-screen" style="display: none;">
<div class="level-select-header">
<h2>🎓 Training Academy - 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;">
<!-- 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 the Gooner Training Academy. Voice narration is ready.");
}, 1000);
}
} else {
console.warn('⚠️ VoiceManager not available');
updateTTSStatus('Voice unavailable');
document.getElementById('tts-toggle').disabled = true;
}
} catch (error) {
console.error('❌ TTS initialization failed:', error);
updateTTSStatus('Voice error');
}
}
// Speak text with TTS
function speakText(text, options = {}) {
if (!ttsEnabled || !voiceManager || !text) return;
// Clean up text for better speech
const cleanText = text
.replace(/🎯|📋|🧘|🎭|📸|🔍|✅|❌|⚠️|🎉|🏆|📊|🎮|🎬|🪞|📷|🎨/g, '') // Remove emojis
.replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold markdown
.replace(/<[^>]*>/g, '') // Remove HTML tags
.replace(/\s+/g, ' ') // Normalize whitespace
.trim();
if (!cleanText) return;
// Stop current speech if speaking
if (currentUtterance) {
voiceManager.stop();
}
console.log('🔊 Speaking:', cleanText.substring(0, 50) + '...');
const speechOptions = {
rate: options.rate || 0.9, // Slightly slower for better comprehension
pitch: options.pitch || 1.1, // Slightly higher pitch
volume: options.volume || 0.8,
onStart: () => {
updateTTSStatus('Speaking...');
},
onEnd: () => {
updateTTSStatus('Voice ready');
currentUtterance = null;
// Process next in queue
if (ttsQueue.length > 0) {
const next = ttsQueue.shift();
setTimeout(() => speakText(next.text, next.options), 500);
}
},
onError: (error) => {
console.error('🔊 TTS error:', error);
updateTTSStatus('Voice error');
currentUtterance = null;
}
};
currentUtterance = voiceManager.speak(cleanText, speechOptions);
}
// Queue text for TTS (useful for multiple announcements)
function queueTTS(text, options = {}) {
if (!ttsEnabled || !voiceManager) return;
if (currentUtterance) {
ttsQueue.push({ text, options });
} else {
speakText(text, options);
}
}
// Toggle TTS on/off
function toggleTTS() {
ttsEnabled = !ttsEnabled;
// Update header toggle button
const headerToggleBtn = document.getElementById('tts-toggle');
// Update sidebar toggle button
const sidebarToggleBtn = document.getElementById('sidebar-tts-toggle');
if (ttsEnabled) {
if (headerToggleBtn) {
headerToggleBtn.innerHTML = '🔊 Voice: ON';
headerToggleBtn.style.background = 'linear-gradient(135deg, #8B5CF6, #EC4899)';
}
if (sidebarToggleBtn) {
sidebarToggleBtn.innerHTML = '🔊 ON';
sidebarToggleBtn.style.background = 'rgba(139, 92, 246, 0.4)';
sidebarToggleBtn.style.borderColor = 'rgba(139, 92, 246, 0.6)';
}
updateTTSStatus('Voice enabled');
speakText('Voice narration enabled.');
} else {
if (headerToggleBtn) {
headerToggleBtn.innerHTML = '🔇 Voice: OFF';
headerToggleBtn.style.background = 'linear-gradient(135deg, #6B7280, #9CA3AF)';
}
if (sidebarToggleBtn) {
sidebarToggleBtn.innerHTML = '🔇 OFF';
sidebarToggleBtn.style.background = 'rgba(107, 114, 128, 0.4)';
sidebarToggleBtn.style.borderColor = 'rgba(107, 114, 128, 0.6)';
}
updateTTSStatus('Voice disabled');
stopTTS();
}
console.log('🔊 TTS toggled:', ttsEnabled ? 'ON' : 'OFF');
}
// Stop current TTS
function stopTTS() {
if (voiceManager) {
voiceManager.stop();
}
currentUtterance = null;
ttsQueue = [];
updateTTSStatus(ttsEnabled ? 'Voice ready' : 'Voice disabled');
}
// Update TTS status display
function updateTTSStatus(status) {
// Update header status
const headerStatusEl = document.getElementById('tts-status-text');
if (headerStatusEl) {
headerStatusEl.textContent = status;
}
// Update sidebar status
const sidebarStatusEl = document.getElementById('sidebar-tts-status');
if (sidebarStatusEl) {
sidebarStatusEl.textContent = status;
}
}
// Announce training mode selection
function announceModeSelection(modeName) {
if (ttsEnabled) {
speakText(`${modeName} selected. Prepare for your training session.`);
}
}
// Initialize sidebar TTS controls to match current state
function initializeSidebarTTSControls() {
const sidebarToggleBtn = document.getElementById('sidebar-tts-toggle');
const sidebarStatusEl = document.getElementById('sidebar-tts-status');
if (sidebarToggleBtn) {
if (ttsEnabled) {
sidebarToggleBtn.innerHTML = '🔊 ON';
sidebarToggleBtn.style.background = 'rgba(139, 92, 246, 0.4)';
sidebarToggleBtn.style.borderColor = 'rgba(139, 92, 246, 0.6)';
} else {
sidebarToggleBtn.innerHTML = '🔇 OFF';
sidebarToggleBtn.style.background = 'rgba(107, 114, 128, 0.4)';
sidebarToggleBtn.style.borderColor = 'rgba(107, 114, 128, 0.6)';
}
}
if (sidebarStatusEl) {
sidebarStatusEl.textContent = ttsEnabled ? 'Voice ready' : 'Voice disabled';
}
console.log('🔊 Sidebar TTS controls initialized');
}
// ===== END TTS FUNCTIONS =====
// Helper function to get format from file path
function getFormatFromPath(filePath) {
const extension = filePath.split('.').pop().toLowerCase();
return extension || 'unknown';
}
// Get available modes from GameModeManager
function getAvailableModes() {
// Show all available training modes
// Training Academy has multiple specialized modes
const allModes = {
'photography-studio': {
name: 'Photography Studio',
description: 'Dedicated webcam photography and dressing sessions',
icon: '📸'
},
'training-academy': {
name: 'Training Academy',
description: 'Structured training programs and challenges',
icon: '🎓'
},
'punishment-gauntlet': {
name: 'Punishment Gauntlet',
description: 'Face intense punishment and humiliation challenges',
icon: '⛓️'
},
'endurance-trials': {
name: 'Endurance Trials',
description: 'Test your limits with marathon sessions',
icon: '💪'
}
};
// Return all available training modes
return allModes;
}
// Initialize Video Library (similar to Quick Play)
async function initializeVideoLibrary() {
try {
console.log('🎬 Training Academy: Initializing video library...');
// Check if we're in Electron environment with proper API access
const isElectron = window.electronAPI && (
window.electronAPI.readVideoDirectory ||
window.electronAPI.readDirectory ||
window.electronAPI.getVideoFiles
);
if (!isElectron) {
console.log('🌐 Electron API not available - checking alternative methods...');
// Try to use the unified video library from desktop file manager if available
if (window.desktopFileManager && window.desktopFileManager.unifiedVideoLibrary) {
const unifiedLibrary = window.desktopFileManager.unifiedVideoLibrary;
trainingVideoLibrary = [...unifiedLibrary];
console.log(`🎬 Using unified video library: ${trainingVideoLibrary.length} videos`);
if (trainingVideoLibrary.length > 0) {
document.getElementById('videoLibraryStatus').innerHTML =
`<span style="color: var(--color-success);">✅ ${trainingVideoLibrary.length} videos loaded (unified library)</span>`;
// Don't auto-play video on setup screen
} else {
document.getElementById('videoLibraryStatus').innerHTML =
'<span style="color: var(--color-warning);">⚠️ No videos in unified library. Use Library management to add directories.</span>';
}
return;
}
document.getElementById('videoLibraryStatus').innerHTML =
'<span style="color: var(--color-warning);">⚠️ Video library unavailable - Electron API not accessible</span>';
return;
}
// Get linked video directories from localStorage
const linkedDirectories = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
console.log('📁 Linked video directories:', linkedDirectories);
if (linkedDirectories.length === 0) {
document.getElementById('videoLibraryStatus').innerHTML =
'<span style="color: var(--color-warning);">⚠️ No video directories linked. Use Library management to add directories.</span>';
return;
}
// Initialize desktop file manager
if (window.desktopFileManager && typeof window.desktopFileManager.init === 'function') {
await window.desktopFileManager.init();
}
trainingVideoLibrary = [];
// Scan each linked directory for videos
for (const directoryData of linkedDirectories) {
try {
// Handle both string paths and directory objects
const directoryPath = typeof directoryData === 'string' ? directoryData : directoryData.path;
if (!directoryPath) {
console.warn('⚠️ Invalid directory data:', directoryData);
continue;
}
console.log(`📂 Scanning directory: ${directoryPath}`);
// Use the same video scanning approach as Quick Play
const videoExtensions = /\.(mp4|webm|avi|mov|mkv|wmv|flv|m4v)$/i;
let files = [];
if (window.electronAPI.readVideoDirectory) {
files = await window.electronAPI.readVideoDirectory(directoryPath);
} else if (window.electronAPI.readVideoDirectoryRecursive) {
files = await window.electronAPI.readVideoDirectoryRecursive(directoryPath);
} else if (window.electronAPI.readDirectory) {
const allFiles = await window.electronAPI.readDirectory(directoryPath);
files = allFiles.filter(file => videoExtensions.test(file.name));
} else if (window.electronAPI.getVideoFiles) {
files = await window.electronAPI.getVideoFiles(directoryPath);
}
if (files && files.length > 0) {
console.log(`📹 Found ${files.length} videos in ${directoryPath}`);
// Add directory info to each video
files.forEach(file => {
trainingVideoLibrary.push({
name: file.name,
path: file.path,
fullPath: file.path,
size: file.size || 0,
duration: file.duration || 0,
directory: directoryPath,
dateAdded: new Date().toISOString(),
category: 'directory'
});
});
} else {
console.log(`📹 No videos found in ${directoryPath}`);
}
} catch (error) {
console.error(`❌ Error scanning directory ${directoryData}:`, error);
}
}
console.log(`🎬 Total videos loaded: ${trainingVideoLibrary.length}`);
// 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';
modeCard.dataset.mode = modeId;
modeCard.onclick = () => selectTrainingMode(modeId);
modeCard.innerHTML = `
<div class="mode-icon">${mode.icon}</div>
<div class="mode-name">${mode.name}</div>
<div class="mode-description">${mode.description}</div>
`;
container.appendChild(modeCard);
});
}
function selectTrainingMode(modeId) {
// Remove previous selection
document.querySelectorAll('.training-mode-card').forEach(card => {
card.classList.remove('selected');
});
// Select new mode
document.querySelector(`[data-mode="${modeId}"]`).classList.add('selected');
selectedTrainingMode = modeId;
// Enable floating start button
const floatingStartBtn = document.getElementById('floating-academy-start-btn');
if (floatingStartBtn) {
floatingStartBtn.disabled = false;
}
const availableModes = getAvailableModes();
console.log(`🎯 Selected training mode: ${availableModes[modeId].name}`);
// TTS announcement
announceModeSelection(availableModes[modeId].name);
// Save the selected mode
const settings = loadAcademySettings();
settings.selectedMode = modeId;
saveAcademySettings(settings);
}
// Setup Screen Initialization and Event Handlers
function initializeAcademySetupScreen() {
console.log('🎓 Initializing academy setup screen...');
// Load saved settings
const savedSettings = loadAcademySettings();
// Video enable/disable
const enableVideoCheckbox = document.getElementById('academy-enable-video');
const videoOptions = document.getElementById('academy-video-options');
const videoOpacitySlider = document.getElementById('academy-video-opacity');
const videoOpacityValue = document.getElementById('academy-video-opacity-value');
if (enableVideoCheckbox && videoOptions) {
enableVideoCheckbox.checked = savedSettings.enableVideo;
videoOptions.style.display = savedSettings.enableVideo ? 'block' : 'none';
enableVideoCheckbox.addEventListener('change', (e) => {
const enabled = e.target.checked;
if (videoOptions) {
videoOptions.style.display = enabled ? 'block' : 'none';
}
// Save setting
const settings = loadAcademySettings();
settings.enableVideo = enabled;
saveAcademySettings(settings);
console.log(`🎬 Video ${enabled ? 'enabled' : 'disabled'}`);
});
}
if (videoOpacitySlider && videoOpacityValue) {
videoOpacitySlider.value = savedSettings.videoOpacity || 0.7;
videoOpacityValue.textContent = Math.round((savedSettings.videoOpacity || 0.7) * 100) + '%';
videoOpacitySlider.addEventListener('input', (e) => {
const opacity = e.target.value;
videoOpacityValue.textContent = Math.round(opacity * 100) + '%';
// Save setting
const settings = loadAcademySettings();
settings.videoOpacity = parseFloat(opacity);
saveAcademySettings(settings);
// Apply to video if playing
const videoContainer = document.getElementById('trainingVideoContainer');
if (videoContainer) {
videoContainer.style.opacity = opacity;
}
});
}
// TTS enable/disable
const enableTTSCheckbox = document.getElementById('academy-enable-tts');
const ttsOptions = document.getElementById('academy-tts-options');
const ttsVolumeSlider = document.getElementById('academy-tts-volume');
const ttsVolumeValue = document.getElementById('academy-tts-volume-value');
if (enableTTSCheckbox && ttsOptions) {
enableTTSCheckbox.checked = savedSettings.enableTTS;
ttsOptions.style.display = savedSettings.enableTTS ? 'block' : 'none';
enableTTSCheckbox.addEventListener('change', (e) => {
const enabled = e.target.checked;
if (ttsOptions) {
ttsOptions.style.display = enabled ? 'block' : 'none';
}
// Save setting
const settings = loadAcademySettings();
settings.enableTTS = enabled;
saveAcademySettings(settings);
console.log(`🔊 TTS ${enabled ? 'enabled' : 'disabled'}`);
});
}
if (ttsVolumeSlider && ttsVolumeValue) {
ttsVolumeSlider.value = savedSettings.ttsVolume || 80;
ttsVolumeValue.textContent = (savedSettings.ttsVolume || 80) + '%';
ttsVolumeSlider.addEventListener('input', (e) => {
const volume = e.target.value;
ttsVolumeValue.textContent = volume + '%';
// Save setting
const settings = loadAcademySettings();
settings.ttsVolume = parseInt(volume);
saveAcademySettings(settings);
});
}
// Floating start button
const floatingStartBtn = document.getElementById('floating-academy-start-btn');
if (floatingStartBtn) {
floatingStartBtn.disabled = !selectedTrainingMode; // Initially disabled until mode selected
floatingStartBtn.addEventListener('click', () => {
// 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 localStorage
const progress = loadAcademyProgress();
// Update progress bar
const completedCount = 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);
const isCompleted = progress.completed[scenario.id] || false;
const 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
window.selectedAcademyLevel = scenario;
window.selectedLevelNumber = levelNum;
// 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';
}
// 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();
}
// Start Training Session (Updated to use setup screen)
function startTrainingSession() {
if (!selectedTrainingMode) {
alert('Please select a training mode first!');
return;
}
console.log(`🚀 Starting training session: ${selectedTrainingMode}`);
// Apply settings before starting
applyAcademySettings();
// Get settings for TTS and video
const settings = loadAcademySettings();
// Start background video if enabled
if (settings.enableVideo && trainingVideoLibrary.length > 0) {
startBackgroundVideo();
}
// TTS announcement
if (settings.enableTTS) {
const availableModes = getAvailableModes();
const modeName = availableModes[selectedTrainingMode]?.name || selectedTrainingMode;
speakText(`Starting ${modeName} training session. Your journey begins now.`);
}
// Hide setup screen
const setupScreen = document.getElementById('academy-setup');
if (setupScreen) {
setupScreen.style.display = 'none';
}
// Hide library status
const libraryStatus = document.querySelector('.library-status');
if (libraryStatus) {
libraryStatus.style.display = 'none';
}
// Minimize header during training - keep all elements visible
const academyHeader = document.querySelector('.academy-header');
if (academyHeader) {
// Make header smaller and less prominent during training
academyHeader.style.padding = '0.5rem';
academyHeader.style.background = 'rgba(26, 26, 26, 0.7)';
}
// Show game interface
const gameInterface = document.getElementById('gameInterface');
gameInterface.style.display = 'block';
// Make video controls less prominent during game
const videoControls = document.getElementById('videoControlsOverlay');
if (videoControls) {
videoControls.style.opacity = '0.5';
}
// Initialize training-specific game mode
initializeTrainingGame();
}
// Initialize Training Game
function initializeTrainingGame() {
try {
console.log('🎓 Initializing Training Academy game...');
// Debug: Check if humiliation data is properly loaded
console.log('🔍 Debug: Checking humiliation data...');
console.log('🔍 window.humiliationGameData exists:', !!window.humiliationGameData);
if (window.humiliationGameData) {
console.log('🔍 Humiliation scenarios:', window.humiliationGameData.scenarios?.length || 0);
console.log('🔍 Humiliation scenario IDs:', window.humiliationGameData.scenarios?.map(s => s.id) || []);
}
// Ensure we have GameModeManager
if (!window.gameModeManager) {
console.error('❌ GameModeManager not available');
alert('Error: Game system not properly loaded. Please refresh the page.');
return;
}
// Set up game mode manager for training
window.gameModeManager.currentMode = selectedTrainingMode;
console.log(`🎯 Set GameModeManager to: ${selectedTrainingMode}`);
// Get scenarios from GameModeManager's scenario collections
let scenarioIds = [];
if (window.gameModeManager.scenarioCollections[selectedTrainingMode]) {
scenarioIds = window.gameModeManager.scenarioCollections[selectedTrainingMode];
console.log(`📋 Found scenario collection for ${selectedTrainingMode}:`, scenarioIds);
} else {
// Fallback scenario IDs for training-academy
scenarioIds = [
'scenario-training-regimen',
'scenario-creative-tasks',
'scenario-training-session',
'scenario-obedience-training'
];
console.log('📋 Using fallback scenario IDs:', scenarioIds);
}
// Load training tasks using GameModeManager
let trainingTasks = [];
try {
trainingTasks = window.gameModeManager.getScenarioModeTasks();
console.log(`📋 Loaded ${trainingTasks.length} tasks from GameModeManager`);
// Debug: Log the actual task structure
if (trainingTasks.length > 0) {
console.log('🔍 First training task structure:', trainingTasks[0]);
console.log('🔍 Task IDs:', trainingTasks.map(t => t.id));
console.log('🔍 Task types:', trainingTasks.map(t => t.interactiveType));
}
} catch (error) {
console.warn('⚠️ GameModeManager task loading failed:', error);
}
// Special handling for specific modes
if (selectedTrainingMode === 'punishment-gauntlet' && window.humiliationGameData && window.humiliationGameData.scenarios) {
console.log('🎯 Punishment Gauntlet mode detected - loading humiliation scenarios directly');
trainingTasks = window.humiliationGameData.scenarios.map(scenario => ({
id: scenario.id,
text: scenario.text,
difficulty: scenario.difficulty,
type: 'main',
interactiveType: scenario.interactiveType,
interactiveData: scenario.interactiveData,
isScenario: true
}));
console.log(`📋 Loaded ${trainingTasks.length} punishment scenarios directly from humiliation data`);
} else if (selectedTrainingMode === 'training-academy' && window.trainingGameData && window.trainingGameData.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();
// 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
if (step.interactiveType) {
console.log(`🎮 Action step with interactive type: ${step.interactiveType}`);
// Create interactive task and display it
const interactiveTask = {
id: `${scenario.id}-${stepId}`,
text: step.story || 'Complete this action',
interactiveType: step.interactiveType,
params: step.params || {},
nextStep: step.nextStep
};
// Store next step for later
window.currentScenarioNextStep = step.nextStep;
// Display using interactive task manager
if (window.game && window.game.interactiveTaskManager) {
// Create a wrapper container
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';
stepHtml = `
<div class="training-task scenario-action">
<h3>🎭 ${scenario.title}</h3>
<div class="scenario-story">
${step.story}
</div>
<div class="action-details">
<h4>📋 Action Required:</h4>
<p class="action-text">${step.actionText}</p>
<p class="duration-text">⏱️ Duration: ${timeDisplay}</p>
</div>
${isPhotoAction ? `
<div class="photo-section" style="margin: 20px 0;">
<div class="webcam-controls">
<div class="photo-capture-controls" style="text-align: center; margin-top: 15px;">
<button id="start-photo-session-btn" onclick="startPhotoSession()" class="start-btn">
📸 Start Photo Session
</button>
<div id="photo-progress" style="display: none; margin-top: 10px;">
<p>📸 Photos taken: <span id="photos-count">0</span> / <span id="photos-needed">3</span></p>
<button onclick="completePhotoSession()" class="end-session-btn" style="margin-top: 10px; background: var(--color-warning); color: white; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer;">
🔚 End Session
</button>
</div>
</div>
</div>
</div>
` : ''}
<div class="action-timer" style="display: block;">
<p>⏱️ Time remaining: <span id="timer-display">${Math.floor(duration/60)}:${(duration%60).toString().padStart(2,'0')}</span></p>
<div class="progress-bar">
<div id="progress-fill" class="progress-fill"></div>
</div>
<p><strong>⚠️ You must complete the full timer to progress.</strong></p>
<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);
}, 100); // Small delay to ensure DOM is ready
} else if (step.type === 'completion') {
// Save Academy progress if this was a level
if (selectedTrainingMode === 'training-academy' && window.selectedAcademyLevel) {
const progress = loadAcademyProgress();
const levelId = window.selectedAcademyLevel.id;
const levelNum = window.selectedLevelNumber;
// Mark level as complete
progress.completed[levelId] = true;
// Advance current level if this was the current level
if (levelNum >= progress.currentLevel) {
progress.currentLevel = levelNum + 1;
}
saveAcademyProgress(progress);
console.log(`✅ Level ${levelNum} completed and saved`);
}
stepHtml = `
<div class="training-task scenario-completion">
<h3>✅ ${scenario.title} - Complete!</h3>
<div class="scenario-story">
${step.story}
</div>
<div class="completion-outcome">
<h4>🎯 Outcome: ${step.outcome || 'Completed'}</h4>
</div>
<div class="training-controls">
${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 {
// Initialize TTS system
await initializeTTS();
// Initialize PlayerStats first
if (typeof PlayerStats !== 'undefined' && !window.playerStats) {
window.playerStats = new PlayerStats();
console.log('📊 PlayerStats initialized in Training Academy');
} else if (window.playerStats) {
console.log('📊 PlayerStats already available');
} else {
console.warn('⚠️ PlayerStats class not available');
}
// Set up training mode selection
setupTrainingModeSelection();
// Initialize setup screen
initializeAcademySetupScreen();
// Initialize libraries
await initializeVideoLibrary();
await initializePhotoLibrary();
console.log('✅ Training Academy ready');
} catch (error) {
console.error('❌ Error initializing Training Academy:', error);
}
}
// ===== ANIMATION EVENT LISTENERS =====
// Listen for level up events
window.addEventListener('levelUp', (event) => {
console.log('🎉 Training Academy: Level up event received:', event.detail);
});
// Listen for achievement events
window.addEventListener('achievementUnlocked', (event) => {
console.log('🏆 Training Academy: Achievement unlocked event received:', event.detail);
});
// Start initialization when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
console.log('🎓 Training Academy DOM loaded');
// Initialize 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') {
// Create a simple data manager for the file manager
const simpleDataManager = {
get: (key) => {
try {
return JSON.parse(localStorage.getItem(key));
} catch {
return null;
}
},
set: (key, value) => {
localStorage.setItem(key, JSON.stringify(value));
}
};
window.desktopFileManager = new DesktopFileManager(simpleDataManager);
console.log('🖥️ Desktop File Manager initialized for training academy');
// Track saved photo IDs to prevent duplicates
const savedPhotoIds = new Set();
// Intercept localStorage.setItem to save photos to file system
const originalSetItem = localStorage.setItem.bind(localStorage);
localStorage.setItem = function(key, value) {
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');
if (xpElement) {
const currentXP = getCurrentScenarioXP();
xpElement.textContent = currentXP;
}
// Update activity status
const activityElement = document.getElementById('scenario-activity');
if (activityElement) {
activityElement.textContent = getScenarioActivityStatus();
}
}
function getCurrentScenarioXP() {
if (!window.game || !window.game.gameState || !window.game.gameState.scenarioXp) {
return 0;
}
return window.game.gameState.scenarioXp.total || 0;
}
function getScenarioActivityStatus() {
if (!window.game || !window.game.gameState || !window.game.gameState.scenarioTracking) {
return 'Ready';
}
const tracking = window.game.gameState.scenarioTracking;
if (tracking.isInWebcamActivity) {
return 'Webcam Active';
} else if (tracking.isInFocusActivity) {
return 'Focus Task';
} else if (window.game.gameState.isRunning) {
return 'In Progress';
} else {
return 'Ready';
}
}
function startScenarioXPTracking() {
// Clear any existing interval
if (window.scenarioXPInterval) {
clearInterval(window.scenarioXPInterval);
}
// Update status bar every second and trigger XP calculations
window.scenarioXPInterval = setInterval(() => {
// Manually trigger scenario XP updates (since timer might be overridden)
if (window.game && window.game.gameState && window.game.gameState.isRunning) {
if (window.game.updateScenarioTimeBasedXp) {
window.game.updateScenarioTimeBasedXp();
}
if (window.game.updateScenarioFocusXp) {
window.game.updateScenarioFocusXp();
}
if (window.game.updateScenarioWebcamXp) {
window.game.updateScenarioWebcamXp();
}
}
updateScenarioStatusBar();
}, 1000);
console.log('📊 Started scenario XP tracking');
}
function stopScenarioXPTracking() {
if (window.scenarioXPInterval) {
clearInterval(window.scenarioXPInterval);
window.scenarioXPInterval = null;
console.log('📊 Stopped scenario XP tracking');
}
}
function saveScenarioXPToPlayerStats() {
if (!window.game || !window.game.gameState || !window.game.gameState.scenarioXp) {
console.log('📊 No scenario XP to save');
return;
}
const totalScenarioXP = window.game.gameState.scenarioXp.total || 0;
if (totalScenarioXP > 0) {
// Award XP to player stats system
if (window.playerStats) {
window.playerStats.awardXP(totalScenarioXP, 'scenario');
console.log(`📊 Awarded ${totalScenarioXP} scenario XP to player stats`);
}
// Also add to overall XP counter (for compatibility with existing system)
if (window.game.dataManager) {
const overallXp = window.game.dataManager.get('overallXp') || 0;
const newOverallXp = overallXp + totalScenarioXP;
window.game.dataManager.set('overallXp', newOverallXp);
console.log(`📊 Added ${totalScenarioXP} XP to overall counter: ${overallXp} → ${newOverallXp}`);
}
} else {
console.log('📊 No scenario XP earned this session');
}
}
// Test function to check XP awarding
window.testXPAwarding = function() {
console.log('🧪 Testing XP awarding system...');
// Check PlayerStats availability
console.log('📊 PlayerStats available:', !!window.playerStats);
if (window.playerStats) {
console.log('📊 Current total XP:', window.playerStats.stats.totalXP);
console.log('📊 Current scenario XP:', window.playerStats.stats.scenarioGameXP);
}
// Check game state
console.log('🎮 Game available:', !!window.game);
if (window.game && window.game.gameState) {
console.log('🎮 Scenario XP in game state:', window.game.gameState.scenarioXp);
console.log('🎮 Photos taken this session:', window.game.gameState.photosTaken || 0);
if (window.game.gameState.scenarioXp) {
console.log('📸 Photo XP in scenario:', window.game.gameState.scenarioXp.photoRewards || 0);
}
}
// Test awarding 10 XP directly
if (window.playerStats) {
const beforeXP = window.playerStats.stats.totalXP;
window.playerStats.awardXP(10, 'scenario');
const afterXP = window.playerStats.stats.totalXP;
console.log(`🧪 Test XP award: ${beforeXP} → ${afterXP} (+${afterXP - beforeXP})`);
}
// Test photo XP via game system
if (window.game && window.game.incrementPhotosTaken) {
console.log('🧪 Testing photo XP via game.incrementPhotosTaken()...');
const beforeScenarioXP = window.game.gameState.scenarioXp?.photoRewards || 0;
window.game.incrementPhotosTaken();
const afterScenarioXP = window.game.gameState.scenarioXp?.photoRewards || 0;
console.log(`📸 Photo XP test: ${beforeScenarioXP} → ${afterScenarioXP} (+${afterScenarioXP - beforeScenarioXP})`);
}
return {
success: true,
playerStats: !!window.playerStats,
game: !!window.game,
currentTotalXP: window.playerStats?.stats.totalXP || 0,
currentScenarioXP: window.playerStats?.stats.scenarioGameXP || 0,
photosTaken: window.game?.gameState?.photosTaken || 0,
scenarioPhotoXP: window.game?.gameState?.scenarioXp?.photoRewards || 0
};
};
// Test function to check webcam manager game connection
window.testWebcamGameConnection = function() {
console.log('🧪 Testing webcam manager game connection...');
if (currentWebcamManager) {
console.log('📷 WebcamManager available:', !!currentWebcamManager);
console.log('📷 WebcamManager has game reference:', !!currentWebcamManager.game);
if (currentWebcamManager.game) {
console.log('🎮 Game incrementPhotosTaken available:', !!currentWebcamManager.game.incrementPhotosTaken);
}
return {
webcamManager: true,
hasGameReference: !!currentWebcamManager.game,
hasIncrementPhotosTaken: !!(currentWebcamManager.game && currentWebcamManager.game.incrementPhotosTaken)
};
} else {
console.log('❌ No webcam manager available');
return { webcamManager: false };
}
};
</script>
</body>
</html>