Files
easystream-main/f_scripts/shared/theme-switcher.js
SamiAhmed7777 d22b3e1c0d feat: Add complete Docker deployment with web-based setup wizard
Major additions:
- Web-based setup wizard (setup.php, setup_wizard.php, setup-wizard.js)
- Production Docker configuration (docker-compose.prod.yml, .env.production)
- Database initialization SQL files (deploy/init_settings.sql)
- Template builder system with drag-and-drop UI
- Advanced features (OAuth, CDN, enhanced analytics, monetization)
- Comprehensive documentation (deployment guides, quick start, feature docs)
- Design system with accessibility and responsive layout
- Deployment automation scripts (deploy.ps1, generate-secrets.ps1)

Setup wizard allows customization of:
- Platform name and branding
- Domain configuration
- Membership tiers and pricing
- Admin credentials
- Feature toggles

Database includes 270+ tables for complete video streaming platform with
advanced features for analytics, moderation, template building, and monetization.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 01:42:31 -07:00

515 lines
14 KiB
JavaScript

/**
* EasyStream Advanced Theme Switcher
* Smooth theme transitions with system preference detection
* Version: 2.0
*/
class ThemeSwitcher {
constructor(options = {}) {
this.options = {
storageKey: 'easystream-theme',
colorStorageKey: 'easystream-color',
transitionDuration: 300,
detectSystemPreference: true,
...options
};
this.themes = {
light: ['blue', 'red', 'cyan', 'green', 'orange', 'pink', 'purple'],
dark: ['darkblue', 'darkred', 'darkcyan', 'darkgreen', 'darkorange', 'darkpink', 'darkpurple']
};
this.currentTheme = null;
this.currentColor = null;
this.systemPreference = null;
this.init();
}
/**
* Initialize theme switcher
*/
init() {
// Detect system preference
if (this.options.detectSystemPreference) {
this.detectSystemPreference();
this.watchSystemPreference();
}
// Load saved theme or use default
const savedTheme = this.getSavedTheme();
const savedColor = this.getSavedColor();
if (savedTheme && savedColor) {
this.applyTheme(savedTheme, savedColor, false);
} else if (this.systemPreference) {
// Use system preference
const defaultColor = 'blue';
const theme = this.systemPreference === 'dark' ? `dark${defaultColor}` : defaultColor;
this.applyTheme(this.systemPreference, defaultColor, false);
} else {
// Default to light blue
this.applyTheme('light', 'blue', false);
}
// Setup UI controls if they exist
this.setupControls();
}
/**
* Detect system color scheme preference
*/
detectSystemPreference() {
if (window.matchMedia) {
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
this.systemPreference = darkModeQuery.matches ? 'dark' : 'light';
}
}
/**
* Watch for system preference changes
*/
watchSystemPreference() {
if (window.matchMedia) {
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
darkModeQuery.addEventListener('change', (e) => {
this.systemPreference = e.matches ? 'dark' : 'light';
// Only auto-switch if user hasn't manually set a theme
const savedTheme = this.getSavedTheme();
if (!savedTheme || savedTheme === 'auto') {
this.applySystemTheme();
}
this.dispatchEvent('system-preference-change', {
preference: this.systemPreference
});
});
}
}
/**
* Apply system theme based on preference
*/
applySystemTheme() {
const color = this.currentColor || 'blue';
const theme = this.systemPreference === 'dark' ? `dark${color}` : color;
this.applyTheme(this.systemPreference, color, true);
}
/**
* Get saved theme from storage
*/
getSavedTheme() {
try {
return localStorage.getItem(this.options.storageKey);
} catch (e) {
return null;
}
}
/**
* Get saved color from storage
*/
getSavedColor() {
try {
return localStorage.getItem(this.options.colorStorageKey);
} catch (e) {
return null;
}
}
/**
* Save theme to storage
*/
saveTheme(mode, color) {
try {
localStorage.setItem(this.options.storageKey, mode);
localStorage.setItem(this.options.colorStorageKey, color);
} catch (e) {
console.warn('Failed to save theme preference', e);
}
}
/**
* Apply theme with smooth transition
* @param {string} mode - 'light' or 'dark'
* @param {string} color - color name (blue, red, etc.)
* @param {boolean} animate - whether to animate the transition
*/
applyTheme(mode, color, animate = true) {
const fullTheme = mode === 'dark' ? `dark${color}` : color;
// Prevent unnecessary updates
if (this.currentTheme === mode && this.currentColor === color) {
return;
}
const previousMode = this.currentTheme;
this.currentTheme = mode;
this.currentColor = color;
// Add transition class for smooth animation
if (animate) {
this.enableTransitions();
}
// Update body/html data attribute
document.documentElement.setAttribute('data-theme', fullTheme);
document.body.setAttribute('data-theme', fullTheme);
// Update meta theme-color for mobile browsers
this.updateMetaThemeColor();
// Save preference
this.saveTheme(mode, color);
// Remove transition class after animation
if (animate) {
setTimeout(() => {
this.disableTransitions();
}, this.options.transitionDuration);
}
// Update UI controls
this.updateControls();
// Dispatch custom event
this.dispatchEvent('theme-change', {
mode,
color,
fullTheme,
previousMode
});
}
/**
* Enable smooth transitions during theme switch
*/
enableTransitions() {
const style = document.createElement('style');
style.id = 'theme-transition-styles';
style.textContent = `
*,
*::before,
*::after {
transition: background-color ${this.options.transitionDuration}ms ease,
color ${this.options.transitionDuration}ms ease,
border-color ${this.options.transitionDuration}ms ease,
box-shadow ${this.options.transitionDuration}ms ease !important;
}
/* Disable transitions for animations that shouldn't be affected */
.no-theme-transition,
[class*="animate"],
.video-js,
.spinner {
transition: none !important;
}
`;
document.head.appendChild(style);
}
/**
* Disable transitions after theme switch
*/
disableTransitions() {
const style = document.getElementById('theme-transition-styles');
if (style) {
style.remove();
}
}
/**
* Update meta theme-color for mobile browsers
*/
updateMetaThemeColor() {
let themeColorMeta = document.querySelector('meta[name="theme-color"]');
if (!themeColorMeta) {
themeColorMeta = document.createElement('meta');
themeColorMeta.name = 'theme-color';
document.head.appendChild(themeColorMeta);
}
// Color mapping
const colors = {
blue: '#06a2cb',
red: '#dd1e2f',
cyan: '#00997a',
green: '#199900',
orange: '#f28410',
pink: '#ec7ab9',
purple: '#b25c8b'
};
const bgColors = {
light: '#ffffff',
dark: '#121212'
};
// Use primary color for theme
const primaryColor = colors[this.currentColor] || colors.blue;
const bgColor = bgColors[this.currentTheme] || bgColors.light;
themeColorMeta.content = this.currentTheme === 'dark' ? bgColor : primaryColor;
}
/**
* Toggle between light and dark mode
*/
toggleMode() {
const newMode = this.currentTheme === 'light' ? 'dark' : 'light';
this.applyTheme(newMode, this.currentColor, true);
}
/**
* Set color theme
* @param {string} color - color name
*/
setColor(color) {
if (!this.themes.light.includes(color) && !this.themes.dark.includes(color.replace('dark', ''))) {
console.warn(`Invalid color: ${color}`);
return;
}
const baseColor = color.replace('dark', '');
this.applyTheme(this.currentTheme, baseColor, true);
}
/**
* Setup UI controls
*/
setupControls() {
// Theme toggle button
const toggleBtn = document.getElementById('theme-toggle');
if (toggleBtn) {
toggleBtn.addEventListener('click', () => this.toggleMode());
}
// Color picker buttons
const colorBtns = document.querySelectorAll('[data-color-theme]');
colorBtns.forEach(btn => {
btn.addEventListener('click', () => {
const color = btn.getAttribute('data-color-theme');
this.setColor(color);
});
});
// Auto theme switch (follow system)
const autoSwitch = document.getElementById('theme-auto');
if (autoSwitch) {
autoSwitch.addEventListener('change', (e) => {
if (e.target.checked) {
this.applySystemTheme();
}
});
}
}
/**
* Update UI controls to reflect current theme
*/
updateControls() {
// Update toggle button
const toggleBtn = document.getElementById('theme-toggle');
if (toggleBtn) {
const icon = toggleBtn.querySelector('i, .icon');
if (icon) {
if (this.currentTheme === 'dark') {
icon.className = 'icon-sun';
toggleBtn.setAttribute('aria-label', 'Switch to light mode');
} else {
icon.className = 'icon-moon';
toggleBtn.setAttribute('aria-label', 'Switch to dark mode');
}
}
}
// Update color buttons
const colorBtns = document.querySelectorAll('[data-color-theme]');
colorBtns.forEach(btn => {
const color = btn.getAttribute('data-color-theme');
if (color === this.currentColor) {
btn.classList.add('active');
btn.setAttribute('aria-pressed', 'true');
} else {
btn.classList.remove('active');
btn.setAttribute('aria-pressed', 'false');
}
});
}
/**
* Get current theme info
*/
getCurrentTheme() {
return {
mode: this.currentTheme,
color: this.currentColor,
fullTheme: this.currentTheme === 'dark' ? `dark${this.currentColor}` : this.currentColor,
systemPreference: this.systemPreference
};
}
/**
* Dispatch custom event
*/
dispatchEvent(eventName, detail) {
const event = new CustomEvent(`easystream:${eventName}`, {
detail,
bubbles: true
});
document.dispatchEvent(event);
}
/**
* Create theme picker UI
* @returns {HTMLElement}
*/
static createThemePicker() {
const container = document.createElement('div');
container.className = 'theme-picker';
container.setAttribute('role', 'toolbar');
container.setAttribute('aria-label', 'Theme controls');
container.innerHTML = `
<div class="theme-picker-header">
<h3>Appearance</h3>
</div>
<div class="theme-mode-toggle">
<label class="theme-label">
<span>Mode</span>
<button id="theme-toggle" class="btn btn-secondary touch-target" aria-label="Toggle theme mode">
<i class="icon-moon"></i>
</button>
</label>
</div>
<div class="theme-color-picker">
<span class="theme-label">Color</span>
<div class="color-options" role="group" aria-label="Color themes">
<button class="color-btn color-blue" data-color-theme="blue" aria-label="Blue theme">
<span class="sr-only">Blue</span>
</button>
<button class="color-btn color-red" data-color-theme="red" aria-label="Red theme">
<span class="sr-only">Red</span>
</button>
<button class="color-btn color-cyan" data-color-theme="cyan" aria-label="Cyan theme">
<span class="sr-only">Cyan</span>
</button>
<button class="color-btn color-green" data-color-theme="green" aria-label="Green theme">
<span class="sr-only">Green</span>
</button>
<button class="color-btn color-orange" data-color-theme="orange" aria-label="Orange theme">
<span class="sr-only">Orange</span>
</button>
<button class="color-btn color-pink" data-color-theme="pink" aria-label="Pink theme">
<span class="sr-only">Pink</span>
</button>
<button class="color-btn color-purple" data-color-theme="purple" aria-label="Purple theme">
<span class="sr-only">Purple</span>
</button>
</div>
</div>
<style>
.theme-picker {
padding: var(--space-lg, 24px);
background: var(--color-bg-elevated);
border-radius: var(--border-radius-lg, 12px);
box-shadow: var(--shadow-md);
}
.theme-picker-header h3 {
margin: 0 0 var(--space-md, 16px) 0;
font-size: var(--font-size-lg, 1.125rem);
font-weight: var(--font-weight-semibold, 600);
}
.theme-mode-toggle {
margin-bottom: var(--space-lg, 24px);
}
.theme-label {
display: flex;
align-items: center;
justify-content: space-between;
font-weight: var(--font-weight-medium, 500);
margin-bottom: var(--space-sm, 8px);
}
.color-options {
display: flex;
gap: var(--space-sm, 8px);
flex-wrap: wrap;
}
.color-btn {
width: 44px;
height: 44px;
border-radius: var(--border-radius-full, 9999px);
border: 3px solid transparent;
cursor: pointer;
transition: all var(--transition-base, 200ms);
position: relative;
}
.color-btn:hover {
transform: scale(1.1);
box-shadow: var(--shadow-md);
}
.color-btn:focus-visible {
outline: 3px solid var(--focus-ring-color);
outline-offset: 2px;
}
.color-btn.active {
border-color: var(--color-text-primary);
box-shadow: var(--shadow-lg);
}
.color-btn.active::after {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-weight: bold;
font-size: 1.25rem;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.color-blue { background: #06a2cb; }
.color-red { background: #dd1e2f; }
.color-cyan { background: #00997a; }
.color-green { background: #199900; }
.color-orange { background: #f28410; }
.color-pink { background: #ec7ab9; }
.color-purple { background: #b25c8b; }
</style>
`;
return container;
}
}
// Auto-initialize on page load
if (typeof window !== 'undefined') {
window.addEventListener('DOMContentLoaded', () => {
window.themeSwitcher = new ThemeSwitcher();
// Make it globally accessible
window.EasyStream = window.EasyStream || {};
window.EasyStream.ThemeSwitcher = ThemeSwitcher;
});
}
// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
module.exports = ThemeSwitcher;
}