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