1006 lines
43 KiB
HTML
1006 lines
43 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Hotel Pi - Settings</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.container {
|
||
background: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||
max-width: 600px;
|
||
width: 100%;
|
||
padding: 40px;
|
||
}
|
||
|
||
h1 {
|
||
color: #333;
|
||
margin-bottom: 10px;
|
||
font-size: 28px;
|
||
}
|
||
|
||
.subtitle {
|
||
color: #666;
|
||
margin-bottom: 30px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 25px;
|
||
}
|
||
|
||
label {
|
||
display: block;
|
||
color: #333;
|
||
font-weight: 500;
|
||
margin-bottom: 8px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
input[type="text"],
|
||
input[type="number"],
|
||
input[type="color"],
|
||
select {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
font-family: inherit;
|
||
transition: border-color 0.2s;
|
||
}
|
||
|
||
input[type="text"]:focus,
|
||
input[type="number"]:focus,
|
||
input[type="color"]:focus,
|
||
select:focus {
|
||
outline: none;
|
||
border-color: #667eea;
|
||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||
}
|
||
|
||
.checkbox-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
input[type="checkbox"] {
|
||
width: 20px;
|
||
height: 20px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.checkbox-group label {
|
||
margin: 0;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.section {
|
||
margin-bottom: 30px;
|
||
padding-bottom: 30px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
|
||
.section:last-child {
|
||
border-bottom: none;
|
||
margin-bottom: 0;
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.section h2 {
|
||
font-size: 16px;
|
||
color: #667eea;
|
||
margin-bottom: 20px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.buttons {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-top: 30px;
|
||
}
|
||
|
||
button {
|
||
flex: 1;
|
||
padding: 12px 24px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.btn-save {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-save:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
||
}
|
||
|
||
.btn-save:active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.btn-reset {
|
||
background: #f0f0f0;
|
||
color: #333;
|
||
}
|
||
|
||
.btn-reset:hover {
|
||
background: #e0e0e0;
|
||
}
|
||
|
||
.message {
|
||
padding: 12px 16px;
|
||
border-radius: 6px;
|
||
margin-bottom: 20px;
|
||
display: none;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.message.success {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
border: 1px solid #c3e6cb;
|
||
display: block;
|
||
}
|
||
|
||
.message.error {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
border: 1px solid #f5c6cb;
|
||
display: block;
|
||
}
|
||
|
||
.info-text {
|
||
font-size: 12px;
|
||
color: #888;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.location-mode {
|
||
display: flex;
|
||
gap: 20px;
|
||
align-items: center;
|
||
margin-top: 15px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.location-mode label {
|
||
margin: 0;
|
||
}
|
||
|
||
#manualLocation {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.color-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
||
gap: 15px;
|
||
margin-top: 15px;
|
||
width: 100%;
|
||
}
|
||
|
||
.color-picker-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.color-picker-item label {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #555;
|
||
margin: 0;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.color-preview {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 8px;
|
||
background: #f9f9f9;
|
||
border-radius: 6px;
|
||
border: 1px solid #eee;
|
||
}
|
||
|
||
.color-preview input[type="color"] {
|
||
width: 50px;
|
||
height: 50px;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
}
|
||
|
||
.color-hex {
|
||
font-size: 12px;
|
||
font-family: monospace;
|
||
color: #666;
|
||
flex: 1;
|
||
}
|
||
|
||
.theme-buttons {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn-secondary {
|
||
padding: 10px 16px;
|
||
background: #4a90e2;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #3a7bc8;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 12px rgba(74, 144, 226, 0.3);
|
||
}
|
||
|
||
.btn-secondary.export {
|
||
background: #10b981;
|
||
}
|
||
|
||
.btn-secondary.export:hover {
|
||
background: #059669;
|
||
}
|
||
|
||
.btn-secondary.import {
|
||
background: #f59e0b;
|
||
}
|
||
|
||
.btn-secondary.import:hover {
|
||
background: #d97706;
|
||
}
|
||
|
||
#themeFileInput {
|
||
display: none;
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
.container {
|
||
padding: 20px;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.buttons {
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>🎛️ Hotel Pi Settings</h1>
|
||
<p class="subtitle">Configure your kiosk display</p>
|
||
|
||
<div class="message" id="message"></div>
|
||
|
||
<form id="settingsForm">
|
||
<!-- Display Settings -->
|
||
<div class="section">
|
||
<h2>Display Settings</h2>
|
||
|
||
<div class="form-group">
|
||
<label for="title">Hero Name (Top Right)</label>
|
||
<input type="text" id="title" name="title" placeholder="e.g., Guest" required>
|
||
<p class="info-text">Shown as the guest/hero name (e.g., "Guest's Family")</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="welcomePrefix">Welcome Prefix</label>
|
||
<input type="text" id="welcomePrefix" name="welcomePrefix" placeholder="e.g., WELCOME" required>
|
||
<p class="info-text">First line above the hero name</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="welcomeSuffix">Welcome Suffix</label>
|
||
<input type="text" id="welcomeSuffix" name="welcomeSuffix" placeholder="e.g., Family">
|
||
<p class="info-text">Appended after guest name (e.g., " Family" displays as "Guest Family")</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="resortName">Resort Name</label>
|
||
<input type="text" id="resortName" name="resortName" placeholder="e.g., Mojo Dojo Casa House" required>
|
||
<p class="info-text">Displayed on both welcome and hero pages</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="roomNumber">Room Number</label>
|
||
<input type="text" id="roomNumber" name="roomNumber" placeholder="e.g., ROOM 201" required>
|
||
<p class="info-text">Displayed on hero page top bar</p>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="form-group">
|
||
<label for="idleTimeout">Idle Timeout (seconds)</label>
|
||
<input type="number" id="idleTimeout" name="idle_timeout_seconds" min="30" max="600" required>
|
||
<p class="info-text">Time before returning to idle screen</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Hero Page Settings -->
|
||
<div class="section">
|
||
<h2>Hero Page</h2>
|
||
|
||
<div class="form-group">
|
||
<label for="backgroundVideoPath">Background Video Path</label>
|
||
<input type="text" id="backgroundVideoPath" name="backgroundVideoPath" placeholder="e.g., /media/background.mp4" required>
|
||
<p class="info-text">Path to video file (e.g., /media/background.mp4)</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="heroWelcomeText">Welcome Text</label>
|
||
<input type="text" id="heroWelcomeText" name="heroWelcomeText" placeholder="e.g., WELCOME" required>
|
||
<p class="info-text">Large text displayed on hero page (e.g., "WELCOME")</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="heroGuestText">Guest Name Text</label>
|
||
<input type="text" id="heroGuestText" name="heroGuestText" placeholder="e.g., Guest" required>
|
||
<p class="info-text">Guest name displayed below welcome text</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="backgroundAudioPath">Background Audio</label>
|
||
<select id="backgroundAudioPath" name="backgroundAudioPath" required>
|
||
<option value="">Loading audio files...</option>
|
||
</select>
|
||
<p class="info-text">Audio file to loop with 1 second crossfade on loop</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="videoTopOffset">Video Top Offset (px)</label>
|
||
<input type="number" id="videoTopOffset" name="videoTopOffset" min="0" max="500" value="0">
|
||
<p class="info-text">Space above the video in pixels (0-500)</p>
|
||
</div>
|
||
|
||
<div class="checkbox-group">
|
||
<input type="checkbox" id="enableHeaderBarOpacity" name="enableHeaderBarOpacity">
|
||
<label for="enableHeaderBarOpacity">Enable Header Bar Opacity (Fade)</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Themes -->
|
||
<div class="section">
|
||
<h2>Color Themes</h2>
|
||
|
||
<div style="margin-bottom: 25px;">
|
||
<h3 style="font-size: 13px; color: #667eea; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 0.5px;">Hero Page Colors</h3>
|
||
<div class="color-grid">
|
||
<div class="color-picker-item">
|
||
<label for="heroStatusBarBg">Status Bar</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="heroStatusBarBg" name="heroStatusBarBg">
|
||
<div class="color-hex" id="heroStatusBarBg-hex">#1B4965</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="color-picker-item">
|
||
<label for="heroHeaderBg">Header Bar</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="heroHeaderBg" name="heroHeaderBg">
|
||
<div class="color-hex" id="heroHeaderBg-hex">#1E8E9F</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="color-picker-item">
|
||
<label for="heroAccentLineColor">Accent Line</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="heroAccentLineColor" name="heroAccentLineColor">
|
||
<div class="color-hex" id="heroAccentLineColor-hex">#FF6F61</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="color-picker-item">
|
||
<label for="heroWelcomePrefixColor">Welcome Prefix</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="heroWelcomePrefixColor" name="heroWelcomePrefixColor">
|
||
<div class="color-hex" id="heroWelcomePrefixColor-hex">#FF6F61</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="color-picker-item">
|
||
<label for="heroWelcomeTextColor">Welcome Text</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="heroWelcomeTextColor" name="heroWelcomeTextColor">
|
||
<div class="color-hex" id="heroWelcomeTextColor-hex">#FF6F61</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="color-picker-item">
|
||
<label for="heroGuestTextColor">Guest Name</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="heroGuestTextColor" name="heroGuestTextColor">
|
||
<div class="color-hex" id="heroGuestTextColor-hex">#ffffff</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="color-picker-item">
|
||
<label for="borderTrimColor">Border Trim</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="borderTrimColor" name="borderTrimColor">
|
||
<div class="color-hex" id="borderTrimColor-hex">#1B4965</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="color-picker-item">
|
||
<label for="navElementsColor">Nav Elements</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="navElementsColor" name="navElementsColor">
|
||
<div class="color-hex" id="navElementsColor-hex">#1B4965</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 25px;">
|
||
<h3 style="font-size: 13px; color: #667eea; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 0.5px;">Hero Background Gradient</h3>
|
||
<div class="color-grid">
|
||
<div class="color-picker-item">
|
||
<label for="heroBgStart">Start</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="heroBgStart" name="heroBgStart">
|
||
<div class="color-hex" id="heroBgStart-hex">#1B6B7A</div>
|
||
</div>
|
||
</div>
|
||
<div class="color-picker-item">
|
||
<label for="heroBgMid">Middle</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="heroBgMid" name="heroBgMid">
|
||
<div class="color-hex" id="heroBgMid-hex">#2EC4B6</div>
|
||
</div>
|
||
</div>
|
||
<div class="color-picker-item">
|
||
<label for="heroBgEnd">End</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="heroBgEnd" name="heroBgEnd">
|
||
<div class="color-hex" id="heroBgEnd-hex">#0F4C5C</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 25px;">
|
||
<h3 style="font-size: 13px; color: #667eea; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 0.5px;">Nav Bar Gradient</h3>
|
||
<div class="color-grid">
|
||
<div class="color-picker-item">
|
||
<label for="navBarStart">Start</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="navBarStart" name="navBarStart">
|
||
<div class="color-hex" id="navBarStart-hex">#F4E1C1</div>
|
||
</div>
|
||
</div>
|
||
<div class="color-picker-item">
|
||
<label for="navBarMid">Middle</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="navBarMid" name="navBarMid">
|
||
<div class="color-hex" id="navBarMid-hex">#EFDBAB</div>
|
||
</div>
|
||
</div>
|
||
<div class="color-picker-item">
|
||
<label for="navBarEnd">End</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="navBarEnd" name="navBarEnd">
|
||
<div class="color-hex" id="navBarEnd-hex">#EAD5A0</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 25px;">
|
||
<h3 style="font-size: 13px; color: #667eea; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 0.5px;">Idle Page Status Bar</h3>
|
||
<div class="color-grid">
|
||
<div class="color-picker-item">
|
||
<label for="idleStatusBarStart">Start</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="idleStatusBarStart" name="idleStatusBarStart">
|
||
<div class="color-hex" id="idleStatusBarStart-hex">#113CCF</div>
|
||
</div>
|
||
</div>
|
||
<div class="color-picker-item">
|
||
<label for="idleStatusBarEnd">End</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="idleStatusBarEnd" name="idleStatusBarEnd">
|
||
<div class="color-hex" id="idleStatusBarEnd-hex">#0f2fa8</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 25px;">
|
||
<h3 style="font-size: 13px; color: #667eea; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 0.5px;">Idle Page Bottom Bar</h3>
|
||
<div class="color-grid">
|
||
<div class="color-picker-item">
|
||
<label for="idleBottomBarStart">Start</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="idleBottomBarStart" name="idleBottomBarStart">
|
||
<div class="color-hex" id="idleBottomBarStart-hex">#113CCF</div>
|
||
</div>
|
||
</div>
|
||
<div class="color-picker-item">
|
||
<label for="idleBottomBarEnd">End</label>
|
||
<div class="color-preview">
|
||
<input type="color" id="idleBottomBarEnd" name="idleBottomBarEnd">
|
||
<div class="color-hex" id="idleBottomBarEnd-hex">#0f2fa8</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Theme Management -->
|
||
<div style="border-top: 2px solid #eee; padding-top: 20px; margin-top: 25px;">
|
||
<h3 style="font-size: 13px; color: #667eea; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 0.5px;">💾 Saved Themes</h3>
|
||
<div class="theme-buttons">
|
||
<button type="button" class="btn-secondary export" onclick="exportTheme()">📥 Export Theme</button>
|
||
<button type="button" class="btn-secondary import" onclick="document.getElementById('themeFileInput').click()">📤 Import Theme</button>
|
||
</div>
|
||
<input type="file" id="themeFileInput" accept=".json" onchange="importTheme(event)">
|
||
<p class="info-text" style="margin-top: 15px;">Export your current theme as a JSON file, or import a previously saved theme file.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Location Settings -->
|
||
<div class="section">
|
||
<h2>Location Settings</h2>
|
||
|
||
<div class="location-mode">
|
||
<label>
|
||
<input type="radio" name="location_mode" value="ip" id="locationIP">
|
||
Use IP Geolocation
|
||
</label>
|
||
<label>
|
||
<input type="radio" name="location_mode" value="manual" id="locationManual">
|
||
Manual Location
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="manualLocation">Location String</label>
|
||
<input type="text" id="manualLocation" name="manual_location" placeholder="e.g., Disneyland, Anaheim, CA">
|
||
<p class="info-text">Used when manual location is selected</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Features -->
|
||
<div class="section">
|
||
<h2>Features</h2>
|
||
|
||
<div class="form-group">
|
||
<div class="checkbox-group">
|
||
<input type="checkbox" id="plexEnabled" name="plex_enabled">
|
||
<label for="plexEnabled">Enable Plex Integration</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<div class="checkbox-group">
|
||
<input type="checkbox" id="restaurantsEnabled" name="restaurants_enabled">
|
||
<label for="restaurantsEnabled">Show Restaurants Section</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<div class="checkbox-group">
|
||
<input type="checkbox" id="attractionsEnabled" name="attractions_enabled">
|
||
<label for="attractionsEnabled">Show Attractions Section</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Buttons -->
|
||
<div class="buttons">
|
||
<button type="submit" class="btn-save">💾 Save Settings</button>
|
||
<button type="reset" class="btn-reset">↻ Reset</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<script>
|
||
const API_URL = '/api/settings';
|
||
const AUDIO_API_URL = '/api/audio-files';
|
||
const form = document.getElementById('settingsForm');
|
||
const message = document.getElementById('message');
|
||
|
||
// Load audio files first, then settings
|
||
(async () => {
|
||
await loadAudioFiles();
|
||
await loadSettings();
|
||
})();
|
||
|
||
async function loadAudioFiles() {
|
||
try {
|
||
const response = await fetch(AUDIO_API_URL);
|
||
const audioFiles = await response.json();
|
||
const select = document.getElementById('backgroundAudioPath');
|
||
|
||
// Clear the loading option
|
||
select.innerHTML = '';
|
||
|
||
// Add audio files
|
||
if (audioFiles.length > 0) {
|
||
audioFiles.forEach(file => {
|
||
const option = document.createElement('option');
|
||
option.value = file.path;
|
||
option.textContent = file.name;
|
||
select.appendChild(option);
|
||
});
|
||
// Auto-select first file
|
||
select.value = audioFiles[0].path;
|
||
console.log('✓ Auto-selected first audio file:', audioFiles[0].name);
|
||
} else {
|
||
const noFilesOption = document.createElement('option');
|
||
noFilesOption.textContent = 'No audio files found in /media/background_audio/';
|
||
noFilesOption.disabled = true;
|
||
select.appendChild(noFilesOption);
|
||
}
|
||
|
||
// Now that options are loaded, populate is ready
|
||
// (loadSettings will be called after this completes)
|
||
return true;
|
||
} catch (error) {
|
||
console.error('Failed to load audio files:', error);
|
||
const select = document.getElementById('backgroundAudioPath');
|
||
select.innerHTML = '<option>Error loading audio files</option>';
|
||
}
|
||
}
|
||
|
||
async function loadSettings() {
|
||
try {
|
||
const response = await fetch(API_URL);
|
||
const settings = await response.json();
|
||
|
||
// Display Settings
|
||
document.getElementById('title').value = settings.title;
|
||
document.getElementById('welcomePrefix').value = settings.welcomePrefix || 'WELCOME';
|
||
document.getElementById('welcomeSuffix').value = settings.welcomeSuffix || " Family";
|
||
document.getElementById('resortName').value = settings.resortName || 'Mojo Dojo Casa House';
|
||
document.getElementById('roomNumber').value = settings.roomNumber || 'ROOM 201';
|
||
|
||
document.getElementById('idleTimeout').value = settings.idle_timeout_seconds;
|
||
|
||
// Hero page
|
||
document.getElementById('backgroundVideoPath').value = settings.backgroundVideoPath || '/media/background.mp4';
|
||
document.getElementById('heroWelcomeText').value = settings.heroWelcomeText || 'WELCOME';
|
||
document.getElementById('heroGuestText').value = settings.heroGuestText || 'Guest';
|
||
|
||
// Set audio path if it exists in settings (or keep auto-selected first)
|
||
if (settings.backgroundAudioPath) {
|
||
document.getElementById('backgroundAudioPath').value = settings.backgroundAudioPath;
|
||
console.log('✓ Audio file loaded:', settings.backgroundAudioPath);
|
||
} else {
|
||
console.log('ℹ No audio path in settings, using dropdown default (first file)');
|
||
}
|
||
|
||
// Video settings
|
||
document.getElementById('videoTopOffset').value = settings.videoTopOffset || '0';
|
||
document.getElementById('enableHeaderBarOpacity').checked = settings.enableHeaderBarOpacity !== false;
|
||
|
||
// Color themes
|
||
document.getElementById('heroStatusBarBg').value = settings.heroStatusBarBg || '#1B4965';
|
||
document.getElementById('heroHeaderBg').value = settings.heroHeaderBg || '#1E8E9F';
|
||
document.getElementById('heroAccentLineColor').value = settings.heroAccentLineColor || '#FF6F61';
|
||
document.getElementById('heroWelcomePrefixColor').value = settings.heroWelcomePrefixColor || '#FF6F61';
|
||
document.getElementById('heroWelcomeTextColor').value = settings.heroWelcomeTextColor || '#FF6F61';
|
||
document.getElementById('heroGuestTextColor').value = settings.heroGuestTextColor || '#ffffff';
|
||
document.getElementById('borderTrimColor').value = settings.borderTrimColor || '#1B4965';
|
||
document.getElementById('navElementsColor').value = settings.navElementsColor || '#1B4965';
|
||
|
||
// Gradient colors
|
||
document.getElementById('heroBgStart').value = settings.heroBgStart || '#1B6B7A';
|
||
document.getElementById('heroBgMid').value = settings.heroBgMid || '#2EC4B6';
|
||
document.getElementById('heroBgEnd').value = settings.heroBgEnd || '#0F4C5C';
|
||
document.getElementById('navBarStart').value = settings.navBarStart || '#F4E1C1';
|
||
document.getElementById('navBarMid').value = settings.navBarMid || '#EFDBAB';
|
||
document.getElementById('navBarEnd').value = settings.navBarEnd || '#EAD5A0';
|
||
|
||
document.getElementById('idleBgColor').value = settings.idleBgColor || '#1a1a1a';
|
||
document.getElementById('idleTitleColor').value = settings.idleTitleColor || '#ffffff';
|
||
document.getElementById('idleSuffixColor').value = settings.idleSuffixColor || '#d4af37';
|
||
document.getElementById('idleStatusBarStart').value = settings.idleStatusBarStart || '#113CCF';
|
||
document.getElementById('idleStatusBarEnd').value = settings.idleStatusBarEnd || '#0f2fa8';
|
||
document.getElementById('idleBottomBarStart').value = settings.idleBottomBarStart || '#113CCF';
|
||
document.getElementById('idleBottomBarEnd').value = settings.idleBottomBarEnd || '#0f2fa8';
|
||
|
||
document.getElementById('plexEnabled').checked = settings.plex_enabled;
|
||
document.getElementById('restaurantsEnabled').checked = settings.restaurants_enabled;
|
||
document.getElementById('attractionsEnabled').checked = settings.attractions_enabled;
|
||
|
||
// Set location mode
|
||
if (settings.use_ip_location) {
|
||
document.getElementById('locationIP').checked = true;
|
||
} else {
|
||
document.getElementById('locationManual').checked = true;
|
||
}
|
||
document.getElementById('manualLocation').value = settings.manual_location || 'Your Hotel Location';
|
||
} catch (error) {
|
||
showMessage('Failed to load settings', 'error');
|
||
}
|
||
}
|
||
|
||
// Handle form submission
|
||
form.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
|
||
const formData = new FormData(form);
|
||
const settings = {
|
||
title: formData.get('title'),
|
||
welcomePrefix: formData.get('welcomePrefix'),
|
||
welcomeSuffix: formData.get('welcomeSuffix'),
|
||
resortName: formData.get('resortName'),
|
||
roomNumber: formData.get('roomNumber'),
|
||
backgroundVideoPath: formData.get('backgroundVideoPath'),
|
||
backgroundAudioPath: formData.get('backgroundAudioPath'),
|
||
videoTopOffset: formData.get('videoTopOffset') || '0',
|
||
enableHeaderBarOpacity: formData.get('enableHeaderBarOpacity') === 'on',
|
||
heroWelcomeText: formData.get('heroWelcomeText'),
|
||
heroGuestText: formData.get('heroGuestText'),
|
||
use_ip_location: document.getElementById('locationIP').checked,
|
||
manual_location: formData.get('manual_location'),
|
||
idle_timeout_seconds: parseInt(formData.get('idle_timeout_seconds')),
|
||
plex_enabled: formData.get('plex_enabled') === 'on',
|
||
restaurants_enabled: formData.get('restaurants_enabled') === 'on',
|
||
attractions_enabled: formData.get('attractions_enabled') === 'on',
|
||
|
||
heroStatusBarBg: formData.get('heroStatusBarBg'),
|
||
heroHeaderBg: formData.get('heroHeaderBg'),
|
||
heroAccentLineColor: formData.get('heroAccentLineColor'),
|
||
heroWelcomePrefixColor: formData.get('heroWelcomePrefixColor'),
|
||
heroWelcomeTextColor: formData.get('heroWelcomeTextColor'),
|
||
heroGuestTextColor: formData.get('heroGuestTextColor'),
|
||
borderTrimColor: formData.get('borderTrimColor'),
|
||
navElementsColor: formData.get('navElementsColor'),
|
||
heroBgStart: formData.get('heroBgStart'),
|
||
heroBgMid: formData.get('heroBgMid'),
|
||
heroBgEnd: formData.get('heroBgEnd'),
|
||
navBarStart: formData.get('navBarStart'),
|
||
navBarMid: formData.get('navBarMid'),
|
||
navBarEnd: formData.get('navBarEnd'),
|
||
idleBgColor: formData.get('idleBgColor'),
|
||
idleTitleColor: formData.get('idleTitleColor'),
|
||
idleSuffixColor: formData.get('idleSuffixColor'),
|
||
idleStatusBarStart: formData.get('idleStatusBarStart'),
|
||
idleStatusBarEnd: formData.get('idleStatusBarEnd'),
|
||
idleBottomBarStart: formData.get('idleBottomBarStart'),
|
||
idleBottomBarEnd: formData.get('idleBottomBarEnd'),
|
||
};
|
||
|
||
try {
|
||
const response = await fetch(API_URL, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(settings),
|
||
});
|
||
|
||
if (response.ok) {
|
||
showMessage('✓ Settings saved successfully!', 'success');
|
||
} else {
|
||
showMessage('Failed to save settings', 'error');
|
||
}
|
||
} catch (error) {
|
||
showMessage('Error saving settings: ' + error.message, 'error');
|
||
}
|
||
});
|
||
|
||
function showMessage(text, type) {
|
||
message.textContent = text;
|
||
message.className = `message ${type}`;
|
||
setTimeout(() => {
|
||
message.className = 'message';
|
||
}, 4000);
|
||
}
|
||
|
||
// Color picker hex display update
|
||
const colorPickerIds = [
|
||
'heroStatusBarBg', 'heroHeaderBg', 'heroAccentLineColor', 'heroWelcomePrefixColor', 'heroWelcomeTextColor', 'heroGuestTextColor',
|
||
'borderTrimColor', 'navElementsColor',
|
||
'heroBgStart', 'heroBgMid', 'heroBgEnd',
|
||
'navBarStart', 'navBarMid', 'navBarEnd',
|
||
'idleBgColor', 'idleTitleColor', 'idleSuffixColor',
|
||
'idleStatusBarStart', 'idleStatusBarEnd', 'idleBottomBarStart', 'idleBottomBarEnd'
|
||
];
|
||
|
||
colorPickerIds.forEach(id => {
|
||
const colorInput = document.getElementById(id);
|
||
if (colorInput) {
|
||
colorInput.addEventListener('input', (e) => {
|
||
const hexDisplay = document.getElementById(id + '-hex');
|
||
if (hexDisplay) {
|
||
hexDisplay.textContent = e.target.value.toUpperCase();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// Export theme as JSON file in themes.js format
|
||
function exportTheme() {
|
||
const formData = new FormData(form);
|
||
const themeKeyName = prompt('Enter theme key (camelCase, e.g. "coastalModern"):', 'customTheme');
|
||
if (themeKeyName === null) return;
|
||
|
||
const themeName = prompt('Enter display name (e.g. "Coastal Modern"):', 'Custom Theme');
|
||
if (themeName === null) return;
|
||
|
||
// Create theme object in themes.js format - include all color fields
|
||
const themeObject = {
|
||
[themeKeyName]: {
|
||
name: themeName,
|
||
statusBar: formData.get('heroStatusBarBg'),
|
||
accentLine: formData.get('heroAccentLineColor'),
|
||
headerBar: formData.get('heroHeaderBg'),
|
||
heroBg: {
|
||
start: formData.get('heroBgStart'),
|
||
mid: formData.get('heroBgMid'),
|
||
end: formData.get('heroBgEnd'),
|
||
},
|
||
navBar: {
|
||
start: formData.get('navBarStart'),
|
||
mid: formData.get('navBarMid'),
|
||
end: formData.get('navBarEnd'),
|
||
},
|
||
borderTrim: formData.get('borderTrimColor'),
|
||
navElements: formData.get('navElementsColor'),
|
||
welcomePrefix: formData.get('heroWelcomePrefixColor'),
|
||
welcomeText: formData.get('heroWelcomeTextColor'),
|
||
// Idle page colors
|
||
idleBg: formData.get('idleBgColor'),
|
||
idleTitle: formData.get('idleTitleColor'),
|
||
idleSuffix: formData.get('idleSuffixColor'),
|
||
idleStatusBarStart: formData.get('idleStatusBarStart'),
|
||
idleStatusBarEnd: formData.get('idleStatusBarEnd'),
|
||
idleBottomBarStart: formData.get('idleBottomBarStart'),
|
||
idleBottomBarEnd: formData.get('idleBottomBarEnd'),
|
||
}
|
||
};
|
||
|
||
// Format with each color on its own line for VS Code detection
|
||
const jsonStr = JSON.stringify(themeObject, null, 4);
|
||
const dataBlob = new Blob([jsonStr], { type: 'application/json' });
|
||
const url = URL.createObjectURL(dataBlob);
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.download = `${themeKeyName}.json`;
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
URL.revokeObjectURL(url);
|
||
showMessage('✓ Theme exported in themes.js format!', 'success');
|
||
}
|
||
|
||
// Import theme from JSON file (themes.js format)
|
||
function importTheme(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
try {
|
||
const themeData = JSON.parse(e.target.result);
|
||
|
||
// Get the first (and typically only) theme from the imported file
|
||
const themeName = Object.keys(themeData)[0];
|
||
if (!themeName) {
|
||
showMessage('❌ Invalid theme format', 'error');
|
||
return;
|
||
}
|
||
|
||
const theme = themeData[themeName];
|
||
|
||
// Single color mappings
|
||
const colorMapping = {
|
||
'statusBar': 'heroStatusBarBg',
|
||
'accentLine': 'heroAccentLineColor',
|
||
'headerBar': 'heroHeaderBg',
|
||
'welcomePrefix': 'heroWelcomePrefixColor',
|
||
'welcomeText': 'heroWelcomeTextColor',
|
||
'navElements': 'navElementsColor',
|
||
'borderTrim': 'borderTrimColor',
|
||
'idleBg': 'idleBgColor',
|
||
'idleTitle': 'idleTitleColor',
|
||
'idleSuffix': 'idleSuffixColor',
|
||
'idleStatusBarStart': 'idleStatusBarStart',
|
||
'idleStatusBarEnd': 'idleStatusBarEnd',
|
||
'idleBottomBarStart': 'idleBottomBarStart',
|
||
'idleBottomBarEnd': 'idleBottomBarEnd',
|
||
};
|
||
|
||
// Apply single colors
|
||
Object.keys(colorMapping).forEach(themeKey => {
|
||
const formKey = colorMapping[themeKey];
|
||
if (theme[themeKey] && typeof theme[themeKey] === 'string') {
|
||
const input = document.getElementById(formKey);
|
||
if (input) {
|
||
input.value = theme[themeKey];
|
||
const hexDisplay = document.getElementById(formKey + '-hex');
|
||
if (hexDisplay) {
|
||
hexDisplay.textContent = theme[themeKey].toUpperCase();
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// Apply gradient colors (heroBg and navBar)
|
||
if (theme.heroBg && typeof theme.heroBg === 'object') {
|
||
['start', 'mid', 'end'].forEach(part => {
|
||
if (theme.heroBg[part]) {
|
||
const input = document.getElementById(`heroBg${part.charAt(0).toUpperCase() + part.slice(1)}`);
|
||
if (input) {
|
||
input.value = theme.heroBg[part];
|
||
const hexDisplay = document.getElementById(`heroBg${part.charAt(0).toUpperCase() + part.slice(1)}-hex`);
|
||
if (hexDisplay) {
|
||
hexDisplay.textContent = theme.heroBg[part].toUpperCase();
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
if (theme.navBar && typeof theme.navBar === 'object') {
|
||
['start', 'mid', 'end'].forEach(part => {
|
||
if (theme.navBar[part]) {
|
||
const input = document.getElementById(`navBar${part.charAt(0).toUpperCase() + part.slice(1)}`);
|
||
if (input) {
|
||
input.value = theme.navBar[part];
|
||
const hexDisplay = document.getElementById(`navBar${part.charAt(0).toUpperCase() + part.slice(1)}-hex`);
|
||
if (hexDisplay) {
|
||
hexDisplay.textContent = theme.navBar[part].toUpperCase();
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
showMessage(`✓ Theme "${theme.name}" imported successfully!`, 'success');
|
||
// Auto-save to backend
|
||
saveSettings();
|
||
} catch (error) {
|
||
console.error('Error importing theme:', error);
|
||
showMessage('❌ Failed to import theme: ' + error.message, 'error');
|
||
}
|
||
};
|
||
reader.readAsText(file);
|
||
// Reset file input
|
||
event.target.value = '';
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|