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>
This commit is contained in:
SamiAhmed7777
2025-10-26 01:42:31 -07:00
parent 0b7e2d0a5b
commit d22b3e1c0d
90 changed files with 22329 additions and 268 deletions

Binary file not shown.

View File

@@ -0,0 +1,907 @@
/**
* Template Builder Styles
* Drag and drop template builder interface
*/
/* ==========================================================================
Variables
========================================================================== */
:root {
--tb-primary: #3b82f6;
--tb-success: #10b981;
--tb-danger: #ef4444;
--tb-warning: #f59e0b;
--tb-secondary: #6b7280;
--tb-bg: #ffffff;
--tb-bg-secondary: #f9fafb;
--tb-bg-tertiary: #f3f4f6;
--tb-border: #e5e7eb;
--tb-text: #111827;
--tb-text-secondary: #6b7280;
--tb-header-height: 60px;
--tb-sidebar-width: 280px;
--tb-toolbar-height: 50px;
--tb-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--tb-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--tb-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--tb-radius: 6px;
--tb-radius-sm: 4px;
--tb-radius-lg: 8px;
--tb-transition: all 0.2s ease;
}
/* Dark Mode */
[data-theme*="dark"] .template-builder,
.template-builder[data-theme*="dark"] {
--tb-bg: #1f2937;
--tb-bg-secondary: #111827;
--tb-bg-tertiary: #374151;
--tb-border: #374151;
--tb-text: #f9fafb;
--tb-text-secondary: #9ca3af;
}
/* ==========================================================================
Layout
========================================================================== */
.template-builder {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
background: var(--tb-bg);
color: var(--tb-text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
z-index: 9999;
}
.tb-header {
height: var(--tb-header-height);
background: var(--tb-bg-secondary);
border-bottom: 1px solid var(--tb-border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
gap: 20px;
}
.tb-header-left,
.tb-header-center,
.tb-header-right {
display: flex;
align-items: center;
gap: 12px;
}
.tb-header-left {
flex: 1;
}
.tb-header-center {
flex: 0 0 auto;
}
.tb-header-right {
flex: 1;
justify-content: flex-end;
}
.tb-main {
flex: 1;
display: flex;
overflow: hidden;
}
/* ==========================================================================
Sidebars
========================================================================== */
.tb-sidebar {
width: var(--tb-sidebar-width);
background: var(--tb-bg-secondary);
border-right: 1px solid var(--tb-border);
display: flex;
flex-direction: column;
overflow: hidden;
transition: var(--tb-transition);
}
.tb-sidebar-right {
border-right: none;
border-left: 1px solid var(--tb-border);
}
.tb-sidebar.collapsed {
width: 0;
border: none;
}
.tb-sidebar-header {
padding: 16px 20px;
border-bottom: 1px solid var(--tb-border);
display: flex;
align-items: center;
justify-content: space-between;
}
.tb-sidebar-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.tb-sidebar-toggle {
background: none;
border: none;
color: var(--tb-text-secondary);
cursor: pointer;
padding: 4px;
border-radius: var(--tb-radius-sm);
transition: var(--tb-transition);
}
.tb-sidebar-toggle:hover {
background: var(--tb-bg-tertiary);
color: var(--tb-text);
}
.tb-sidebar-content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
/* ==========================================================================
Canvas
========================================================================== */
.tb-canvas-container {
flex: 1;
display: flex;
flex-direction: column;
background: var(--tb-bg-tertiary);
overflow: hidden;
}
.tb-canvas-toolbar {
height: var(--tb-toolbar-height);
background: var(--tb-bg-secondary);
border-bottom: 1px solid var(--tb-border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
}
.tb-zoom-controls {
display: flex;
align-items: center;
gap: 8px;
}
.tb-zoom-level {
min-width: 50px;
text-align: center;
font-size: 14px;
color: var(--tb-text-secondary);
}
.tb-canvas-options {
display: flex;
gap: 16px;
}
.tb-canvas-wrapper {
flex: 1;
overflow: auto;
padding: 40px;
}
.tb-canvas {
background: white;
min-height: 100%;
margin: 0 auto;
box-shadow: var(--tb-shadow-lg);
position: relative;
transition: width 0.3s ease;
}
.tb-canvas[data-device="desktop"] {
width: 100%;
max-width: 1400px;
}
.tb-canvas[data-device="tablet"] {
width: 768px;
}
.tb-canvas[data-device="mobile"] {
width: 375px;
}
.tb-canvas.show-grid {
background-image:
linear-gradient(rgba(0, 0, 0, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 0, 0, 0.03) 1px, transparent 1px);
background-size: 20px 20px;
}
.tb-sections-container {
min-height: 400px;
}
/* Drop Hint */
.tb-drop-hint {
padding: 80px 40px;
text-align: center;
color: var(--tb-text-secondary);
border: 2px dashed var(--tb-border);
border-radius: var(--tb-radius-lg);
margin: 40px;
}
.tb-drop-hint-content i {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.tb-drop-hint-content p {
margin: 0;
font-size: 16px;
}
.tb-drop-hint.hidden {
display: none;
}
/* ==========================================================================
Sections
========================================================================== */
.tb-section {
position: relative;
padding: 20px;
margin: 20px 0;
border: 2px dashed transparent;
transition: var(--tb-transition);
}
.tb-section:hover {
border-color: var(--tb-border);
}
.tb-section.selected {
border-color: var(--tb-primary);
background: rgba(59, 130, 246, 0.05);
}
.tb-section.drag-over {
border-color: var(--tb-success);
background: rgba(16, 185, 129, 0.05);
}
.tb-section-controls {
position: absolute;
top: -12px;
right: 10px;
background: white;
border: 1px solid var(--tb-border);
border-radius: var(--tb-radius);
display: none;
gap: 4px;
padding: 4px;
box-shadow: var(--tb-shadow);
}
.tb-section:hover .tb-section-controls,
.tb-section.selected .tb-section-controls {
display: flex;
}
.tb-columns {
display: grid;
gap: 20px;
}
/* ==========================================================================
Blocks (Components)
========================================================================== */
.tb-block {
position: relative;
min-height: 50px;
border: 2px dashed transparent;
border-radius: var(--tb-radius);
transition: var(--tb-transition);
}
.tb-block:hover {
border-color: var(--tb-border);
}
.tb-block.selected {
border-color: var(--tb-primary);
background: rgba(59, 130, 246, 0.05);
}
.tb-block.dragging {
opacity: 0.5;
}
.tb-block-controls {
position: absolute;
top: -12px;
right: 10px;
background: white;
border: 1px solid var(--tb-border);
border-radius: var(--tb-radius);
display: none;
gap: 4px;
padding: 4px;
box-shadow: var(--tb-shadow);
z-index: 10;
}
.tb-block:hover .tb-block-controls,
.tb-block.selected .tb-block-controls {
display: flex;
}
/* ==========================================================================
Components List (Sidebar)
========================================================================== */
.tb-search-box {
padding: 16px;
position: relative;
}
.tb-search-box input {
width: 100%;
padding: 8px 12px 8px 36px;
border: 1px solid var(--tb-border);
border-radius: var(--tb-radius);
background: var(--tb-bg);
color: var(--tb-text);
font-size: 14px;
}
.tb-search-box i {
position: absolute;
left: 28px;
top: 50%;
transform: translateY(-50%);
color: var(--tb-text-secondary);
}
.tb-component-categories {
padding: 0 16px 16px;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tb-category-btn {
padding: 6px 12px;
border: 1px solid var(--tb-border);
background: var(--tb-bg);
color: var(--tb-text);
border-radius: var(--tb-radius);
font-size: 13px;
cursor: pointer;
transition: var(--tb-transition);
}
.tb-category-btn:hover {
background: var(--tb-bg-tertiary);
}
.tb-category-btn.active {
background: var(--tb-primary);
border-color: var(--tb-primary);
color: white;
}
.tb-components-list {
padding: 0 16px 16px;
}
.tb-component-item {
padding: 12px;
border: 1px solid var(--tb-border);
border-radius: var(--tb-radius);
margin-bottom: 12px;
cursor: grab;
background: var(--tb-bg);
transition: var(--tb-transition);
}
.tb-component-item:hover {
border-color: var(--tb-primary);
box-shadow: var(--tb-shadow);
}
.tb-component-item:active {
cursor: grabbing;
}
.tb-component-item.dragging {
opacity: 0.5;
}
.tb-component-thumb {
width: 100%;
height: 100px;
background: var(--tb-bg-tertiary);
border-radius: var(--tb-radius-sm);
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.tb-component-thumb img {
max-width: 100%;
max-height: 100%;
}
.tb-component-thumb i {
font-size: 32px;
color: var(--tb-text-secondary);
}
.tb-component-name {
font-size: 14px;
font-weight: 500;
margin: 0 0 4px;
}
.tb-component-desc {
font-size: 12px;
color: var(--tb-text-secondary);
margin: 0;
}
/* ==========================================================================
Properties Panel
========================================================================== */
.tb-no-selection {
padding: 40px 20px;
text-align: center;
color: var(--tb-text-secondary);
}
.tb-no-selection i {
font-size: 48px;
margin-bottom: 12px;
opacity: 0.5;
}
.tb-properties-section {
padding: 20px;
}
.tb-properties-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.tb-properties-header h4 {
margin: 0;
font-size: 14px;
font-weight: 600;
}
/* Form Elements */
.tb-form-group {
margin-bottom: 16px;
}
.tb-form-group label {
display: block;
font-size: 13px;
font-weight: 500;
margin-bottom: 6px;
color: var(--tb-text);
}
.tb-input,
.tb-select,
.tb-textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--tb-border);
border-radius: var(--tb-radius);
background: var(--tb-bg);
color: var(--tb-text);
font-size: 14px;
font-family: inherit;
transition: var(--tb-transition);
}
.tb-input:focus,
.tb-select:focus,
.tb-textarea:focus {
outline: none;
border-color: var(--tb-primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.tb-input-sm {
padding: 6px 8px;
font-size: 13px;
}
.tb-textarea {
resize: vertical;
min-height: 80px;
}
input[type="color"].tb-input {
height: 40px;
padding: 4px;
}
.tb-checkbox {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
cursor: pointer;
user-select: none;
}
.tb-checkbox input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.tb-spacing-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
/* Collapsible */
.tb-collapsible {
border-top: 1px solid var(--tb-border);
margin-top: 16px;
padding-top: 16px;
}
.tb-collapsible-header {
width: 100%;
padding: 8px 0;
background: none;
border: none;
text-align: left;
font-size: 13px;
font-weight: 600;
color: var(--tb-text);
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.tb-collapsible-header i {
transition: transform 0.2s;
}
.tb-collapsible.open .tb-collapsible-header i {
transform: rotate(180deg);
}
.tb-collapsible-content {
display: none;
padding-top: 12px;
}
.tb-collapsible.open .tb-collapsible-content {
display: block;
}
/* ==========================================================================
Buttons
========================================================================== */
.tb-btn {
padding: 8px 16px;
border: 1px solid transparent;
border-radius: var(--tb-radius);
font-size: 14px;
font-weight: 500;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
transition: var(--tb-transition);
white-space: nowrap;
}
.tb-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.tb-btn-primary {
background: var(--tb-primary);
color: white;
}
.tb-btn-primary:hover:not(:disabled) {
background: #2563eb;
}
.tb-btn-success {
background: var(--tb-success);
color: white;
}
.tb-btn-success:hover:not(:disabled) {
background: #059669;
}
.tb-btn-danger {
background: var(--tb-danger);
color: white;
}
.tb-btn-danger:hover:not(:disabled) {
background: #dc2626;
}
.tb-btn-secondary {
background: var(--tb-bg-tertiary);
color: var(--tb-text);
border-color: var(--tb-border);
}
.tb-btn-secondary:hover:not(:disabled) {
background: var(--tb-bg-secondary);
}
.tb-btn-icon {
padding: 8px;
min-width: 36px;
justify-content: center;
}
.tb-btn-sm {
padding: 6px 12px;
font-size: 13px;
}
.tb-btn i {
font-size: 16px;
}
/* Device Preview Buttons */
.tb-device-preview {
display: flex;
gap: 4px;
background: var(--tb-bg-tertiary);
padding: 4px;
border-radius: var(--tb-radius);
}
.tb-device-btn {
padding: 8px 12px;
background: transparent;
border: none;
color: var(--tb-text-secondary);
cursor: pointer;
border-radius: var(--tb-radius-sm);
transition: var(--tb-transition);
}
.tb-device-btn:hover {
color: var(--tb-text);
}
.tb-device-btn.active {
background: var(--tb-primary);
color: white;
}
/* Template Name Input */
.tb-template-name-input {
padding: 6px 12px;
border: 1px solid transparent;
border-radius: var(--tb-radius);
background: var(--tb-bg-tertiary);
color: var(--tb-text);
font-size: 15px;
font-weight: 500;
max-width: 300px;
transition: var(--tb-transition);
}
.tb-template-name-input:focus {
outline: none;
border-color: var(--tb-primary);
background: var(--tb-bg);
}
/* ==========================================================================
Modal
========================================================================== */
.tb-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99999;
}
.tb-modal-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.tb-modal-content {
position: relative;
background: var(--tb-bg);
max-width: 500px;
margin: 100px auto;
border-radius: var(--tb-radius-lg);
box-shadow: var(--tb-shadow-lg);
}
.tb-modal-large {
max-width: 90vw;
}
.tb-modal-header {
padding: 20px;
border-bottom: 1px solid var(--tb-border);
display: flex;
align-items: center;
justify-content: space-between;
}
.tb-modal-header h3 {
margin: 0;
font-size: 18px;
}
.tb-modal-close {
background: none;
border: none;
color: var(--tb-text-secondary);
cursor: pointer;
padding: 4px;
border-radius: var(--tb-radius-sm);
transition: var(--tb-transition);
}
.tb-modal-close:hover {
background: var(--tb-bg-tertiary);
color: var(--tb-text);
}
.tb-modal-body {
padding: 20px;
}
.tb-modal-footer {
padding: 20px;
border-top: 1px solid var(--tb-border);
display: flex;
justify-content: flex-end;
gap: 12px;
}
/* ==========================================================================
Loading
========================================================================== */
.tb-loading {
padding: 40px;
text-align: center;
color: var(--tb-text-secondary);
font-size: 14px;
}
/* ==========================================================================
Utilities
========================================================================== */
.hidden {
display: none !important;
}
/* Drag and Drop */
.tb-dragging {
opacity: 0.5;
}
.tb-drag-placeholder {
border: 2px dashed var(--tb-primary);
background: rgba(59, 130, 246, 0.05);
border-radius: var(--tb-radius);
min-height: 100px;
margin: 10px 0;
}
/* Scrollbar */
.tb-sidebar-content::-webkit-scrollbar,
.tb-canvas-wrapper::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.tb-sidebar-content::-webkit-scrollbar-track,
.tb-canvas-wrapper::-webkit-scrollbar-track {
background: var(--tb-bg-secondary);
}
.tb-sidebar-content::-webkit-scrollbar-thumb,
.tb-canvas-wrapper::-webkit-scrollbar-thumb {
background: var(--tb-border);
border-radius: 4px;
}
.tb-sidebar-content::-webkit-scrollbar-thumb:hover,
.tb-canvas-wrapper::-webkit-scrollbar-thumb:hover {
background: var(--tb-text-secondary);
}
/* ==========================================================================
Responsive
========================================================================== */
@media (max-width: 1200px) {
.tb-sidebar {
width: 240px;
}
}
@media (max-width: 768px) {
.tb-header {
padding: 0 12px;
}
.tb-sidebar {
position: absolute;
top: var(--tb-header-height);
bottom: 0;
z-index: 100;
box-shadow: var(--tb-shadow-lg);
}
.tb-sidebar-left {
left: 0;
}
.tb-sidebar-right {
right: 0;
}
.tb-canvas-wrapper {
padding: 20px;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,423 @@
/**
* EasyStream Setup Wizard - Frontend Logic
* Handles wizard navigation, form validation, and AJAX submissions
*/
let currentStep = 1;
const totalSteps = 9;
let setupData = {};
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
updateProgressBar();
attachEventListeners();
loadSavedData();
});
function attachEventListeners() {
// Color picker updates
document.getElementById('primaryColor').addEventListener('input', function(e) {
document.getElementById('primaryColorHex').textContent = e.target.value;
});
document.getElementById('secondaryColor').addEventListener('input', function(e) {
document.getElementById('secondaryColorHex').textContent = e.target.value;
});
// Save form data on input
const inputs = document.querySelectorAll('input, select, textarea');
inputs.forEach(input => {
input.addEventListener('change', saveFormData);
});
}
function changeStep(direction) {
const nextStep = currentStep + direction;
// Validate before moving forward
if (direction > 0) {
if (!validateCurrentStep()) {
return;
}
}
// Special handling for certain steps
if (currentStep === 7 && direction > 0) {
// Review step - show summary and install
showReviewSummary();
startInstallation();
}
if (nextStep >= 1 && nextStep <= totalSteps) {
// Hide current step
document.getElementById(`step${currentStep}`).classList.remove('active');
// Show next step
currentStep = nextStep;
document.getElementById(`step${currentStep}`).classList.add('active');
// Update progress bar
updateProgressBar();
// Update button visibility
updateButtons();
// Scroll to top
document.querySelector('.wizard-body').scrollTop = 0;
}
}
function updateProgressBar() {
const progress = ((currentStep - 1) / (totalSteps - 1)) * 100;
document.getElementById('progressBar').style.width = progress + '%';
}
function updateButtons() {
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
// Show/hide previous button
prevBtn.style.display = currentStep > 1 && currentStep < 8 ? 'block' : 'none';
// Update next button text and visibility
if (currentStep === 1) {
nextBtn.textContent = 'Get Started →';
} else if (currentStep === 7) {
nextBtn.textContent = 'Install Now →';
} else if (currentStep === 8 || currentStep === 9) {
nextBtn.style.display = 'none';
} else {
nextBtn.textContent = 'Next →';
}
}
function validateCurrentStep() {
clearAlert();
switch (currentStep) {
case 1:
// Welcome page, no validation needed
return true;
case 2:
// Platform configuration
const platformName = document.getElementById('platformName').value.trim();
const domainName = document.getElementById('domainName').value.trim();
const contactEmail = document.getElementById('contactEmail').value.trim();
if (!platformName) {
showAlert('error', 'Please enter a platform name');
return false;
}
if (!domainName) {
showAlert('error', 'Please enter a domain name');
return false;
}
if (!contactEmail || !isValidEmail(contactEmail)) {
showAlert('error', 'Please enter a valid contact email');
return false;
}
return true;
case 3:
// Branding, no strict validation
return true;
case 4:
// Membership tiers
const tier1Name = document.getElementById('tier1Name').value.trim();
const tier2Name = document.getElementById('tier2Name').value.trim();
const tier3Name = document.getElementById('tier3Name').value.trim();
if (!tier1Name || !tier2Name || !tier3Name) {
showAlert('error', 'Please enter names for all membership tiers');
return false;
}
return true;
case 5:
// Admin account
const username = document.getElementById('adminUsername').value.trim();
const email = document.getElementById('adminEmail').value.trim();
const password = document.getElementById('adminPassword').value;
const passwordConfirm = document.getElementById('adminPasswordConfirm').value;
if (!username || username.length < 4) {
showAlert('error', 'Username must be at least 4 characters');
return false;
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
showAlert('error', 'Username can only contain letters, numbers, and underscores');
return false;
}
if (!email || !isValidEmail(email)) {
showAlert('error', 'Please enter a valid admin email');
return false;
}
if (!password || password.length < 8) {
showAlert('error', 'Password must be at least 8 characters long');
return false;
}
if (!/[A-Z]/.test(password) || !/[a-z]/.test(password) || !/[0-9]/.test(password)) {
showAlert('error', 'Password must contain uppercase, lowercase, and numbers');
return false;
}
if (password !== passwordConfirm) {
showAlert('error', 'Passwords do not match');
return false;
}
return true;
case 6:
// Features, no strict validation
return true;
default:
return true;
}
}
function isValidEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
function saveFormData() {
// Save to localStorage for recovery
const formData = collectFormData();
localStorage.setItem('easystreamSetup', JSON.stringify(formData));
}
function loadSavedData() {
// Load from localStorage if available
const saved = localStorage.getItem('easystreamSetup');
if (saved) {
try {
const data = JSON.parse(saved);
// Populate form fields
Object.keys(data).forEach(key => {
const element = document.getElementById(key);
if (element) {
if (element.type === 'checkbox') {
element.checked = data[key];
} else {
element.value = data[key];
}
}
});
} catch (e) {
console.error('Failed to load saved data:', e);
}
}
}
function collectFormData() {
const formData = {};
// Platform config
formData.platformName = document.getElementById('platformName').value;
formData.platformTagline = document.getElementById('platformTagline').value;
formData.domainName = document.getElementById('domainName').value;
formData.contactEmail = document.getElementById('contactEmail').value;
formData.timezone = document.getElementById('timezone').value;
// Branding
formData.primaryColor = document.getElementById('primaryColor').value;
formData.secondaryColor = document.getElementById('secondaryColor').value;
formData.defaultTheme = document.getElementById('defaultTheme').value;
formData.enableTheming = document.getElementById('enableTheming').checked;
// Membership tiers
formData.tier1Name = document.getElementById('tier1Name').value;
formData.tier1Upload = document.getElementById('tier1Upload').value;
formData.tier1Storage = document.getElementById('tier1Storage').value;
formData.tier2Name = document.getElementById('tier2Name').value;
formData.tier2Upload = document.getElementById('tier2Upload').value;
formData.tier2Storage = document.getElementById('tier2Storage').value;
formData.tier2Price = document.getElementById('tier2Price').value;
formData.tier3Name = document.getElementById('tier3Name').value;
formData.tier3Upload = document.getElementById('tier3Upload').value;
formData.tier3Storage = document.getElementById('tier3Storage').value;
formData.tier3Price = document.getElementById('tier3Price').value;
// Admin account
formData.adminUsername = document.getElementById('adminUsername').value;
formData.adminEmail = document.getElementById('adminEmail').value;
formData.adminPassword = document.getElementById('adminPassword').value;
formData.adminPasswordConfirm = document.getElementById('adminPasswordConfirm').value;
formData.adminDisplayName = document.getElementById('adminDisplayName').value;
// Features
formData.enableRegistration = document.getElementById('enableRegistration').checked;
formData.enableEmailVerification = document.getElementById('enableEmailVerification').checked;
formData.enableLiveStreaming = document.getElementById('enableLiveStreaming').checked;
formData.enableComments = document.getElementById('enableComments').checked;
formData.enableDownloads = document.getElementById('enableDownloads').checked;
formData.enableMonetization = document.getElementById('enableMonetization').checked;
formData.enableTemplateBuilder = document.getElementById('enableTemplateBuilder').checked;
formData.enableAnalytics = document.getElementById('enableAnalytics').checked;
return formData;
}
function showReviewSummary() {
const data = collectFormData();
const summary = document.getElementById('reviewSummary');
summary.innerHTML = `
<div style="margin-bottom: 24px;">
<h3 style="color: #667eea; margin-bottom: 12px;">Platform Configuration</h3>
<p><strong>Name:</strong> ${data.platformName}</p>
<p><strong>Domain:</strong> ${data.domainName}</p>
<p><strong>Email:</strong> ${data.contactEmail}</p>
<p><strong>Timezone:</strong> ${data.timezone}</p>
</div>
<div style="margin-bottom: 24px;">
<h3 style="color: #667eea; margin-bottom: 12px;">Membership Tiers</h3>
<p><strong>${data.tier1Name}:</strong> ${data.tier1Upload}MB upload, ${data.tier1Storage}GB storage</p>
<p><strong>${data.tier2Name}:</strong> ${data.tier2Upload}MB upload, ${data.tier2Storage}GB storage ($${data.tier2Price}/month)</p>
<p><strong>${data.tier3Name}:</strong> ${data.tier3Upload}MB upload, ${data.tier3Storage}GB storage ($${data.tier3Price}/month)</p>
</div>
<div style="margin-bottom: 24px;">
<h3 style="color: #667eea; margin-bottom: 12px;">Admin Account</h3>
<p><strong>Username:</strong> ${data.adminUsername}</p>
<p><strong>Email:</strong> ${data.adminEmail}</p>
<p><strong>Display Name:</strong> ${data.adminDisplayName || 'Administrator'}</p>
</div>
<div>
<h3 style="color: #667eea; margin-bottom: 12px;">Enabled Features</h3>
<p>${data.enableRegistration ? '✓' : '✗'} User Registration</p>
<p>${data.enableLiveStreaming ? '✓' : '✗'} Live Streaming</p>
<p>${data.enableComments ? '✓' : '✗'} Video Comments</p>
<p>${data.enableMonetization ? '✓' : '✗'} Monetization</p>
<p>${data.enableTemplateBuilder ? '✓' : '✗'} Template Builder</p>
<p>${data.enableAnalytics ? '✓' : '✗'} Analytics</p>
</div>
`;
}
async function startInstallation() {
// Move to installation step
document.getElementById('step7').classList.remove('active');
currentStep = 8;
document.getElementById('step8').classList.add('active');
updateProgressBar();
updateButtons();
const data = collectFormData();
setupData = data;
try {
// Step 1: Save configuration
await installStep('Saving configuration...', async () => {
const response = await fetch('setup.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ action: 'save_configuration', ...data })
});
return await response.json();
});
// Step 2: Create admin user
await installStep('Creating admin account...', async () => {
const response = await fetch('setup.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ action: 'create_admin', ...data })
});
return await response.json();
});
// Step 3: Finalize setup
await installStep('Finalizing setup...', async () => {
const response = await fetch('setup.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ action: 'finalize', ...data })
});
return await response.json();
});
// Success! Move to final step
document.getElementById('step8').classList.remove('active');
currentStep = 9;
document.getElementById('step9').classList.add('active');
updateProgressBar();
// Populate final details
document.getElementById('finalPlatformName').textContent = data.platformName;
document.getElementById('finalDomainName').textContent = data.domainName;
document.getElementById('finalAdminUsername').textContent = data.adminUsername;
// Clear saved data
localStorage.removeItem('easystreamSetup');
} catch (error) {
document.getElementById('installStatus').textContent = 'Installation Failed';
document.getElementById('installStep').innerHTML = `
<div style="color: #e53e3e; padding: 20px; background: #fed7d7; border-radius: 8px;">
<strong>Error:</strong> ${error.message}
<br><br>
<button onclick="location.reload()" class="btn btn-primary">Try Again</button>
</div>
`;
}
}
async function installStep(message, action) {
document.getElementById('installStep').textContent = message;
// Add progress item
const progress = document.getElementById('installProgress');
const item = document.createElement('div');
item.style.cssText = 'padding: 12px; background: #f7fafc; border-radius: 6px; margin-bottom: 8px; display: flex; align-items: center;';
item.innerHTML = `<span class="spinner" style="width: 16px; height: 16px; margin-right: 12px; border-width: 2px;"></span><span>${message}</span>`;
progress.appendChild(item);
try {
const result = await action();
if (!result.success) {
throw new Error(result.error || 'Unknown error occurred');
}
// Update to success
item.innerHTML = `<span style="color: #48bb78; font-size: 20px; margin-right: 12px;">✓</span><span>${message} <strong style="color: #48bb78;">Done!</strong></span>`;
return result;
} catch (error) {
item.innerHTML = `<span style="color: #e53e3e; font-size: 20px; margin-right: 12px;">✗</span><span>${message} <strong style="color: #e53e3e;">Failed!</strong></span>`;
throw error;
}
}
function showAlert(type, message) {
const alert = document.getElementById('alert');
alert.className = `alert alert-${type} show`;
alert.textContent = message;
// Auto-hide after 5 seconds
setTimeout(() => {
alert.classList.remove('show');
}, 5000);
}
function clearAlert() {
const alert = document.getElementById('alert');
alert.classList.remove('show');
}

View File

@@ -0,0 +1,515 @@
/**
* EasyStream Accessibility Enhancements
* WCAG 2.1 AA Compliance CSS
* Version: 1.0
*/
/* ==================== FOCUS INDICATORS ==================== */
/* Enhanced focus styles for keyboard navigation */
*:focus {
outline: none; /* Remove default */
}
*:focus-visible {
outline: 3px solid var(--focus-ring-color, #06a2cb);
outline-offset: 2px;
border-radius: 4px;
}
/* Specific focus styles for interactive elements */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
[role="button"]:focus-visible,
[role="link"]:focus-visible,
[tabindex]:focus-visible {
outline: 3px solid var(--focus-ring-color, #06a2cb);
outline-offset: 2px;
}
/* Video player controls focus */
.video-js button:focus-visible,
.vjs-control:focus-visible {
outline: 3px solid #fff;
outline-offset: 2px;
background: rgba(255, 255, 255, 0.2);
}
/* ==================== CONTRAST IMPROVEMENTS ==================== */
/* Ensure minimum 4.5:1 contrast ratio for normal text */
body {
color: var(--color-text-primary, #111827);
background: var(--color-bg-primary, #ffffff);
}
[data-theme*="dark"] body {
color: var(--color-text-primary, #f0f0f0);
background: var(--color-bg-primary, #121212);
}
/* Link contrast */
a {
color: var(--secondary-color, #0793e2);
text-decoration-skip-ink: auto;
}
a:hover,
a:focus {
color: var(--primary-color, #06a2cb);
text-decoration: underline;
}
/* Button contrast */
.btn,
button {
/* Ensure text contrast on buttons */
position: relative;
}
.btn-primary,
.button-blue {
background: var(--primary-color, #06a2cb);
color: #ffffff;
border: 2px solid var(--primary-color, #06a2cb);
}
.btn-primary:hover,
.button-blue:hover {
background: var(--third-color, #92cefb);
border-color: var(--third-color, #92cefb);
}
/* ==================== TEXT SIZING & READABILITY ==================== */
/* Responsive font sizes */
html {
font-size: 16px; /* Base size for 1rem */
}
@media (max-width: 768px) {
html {
font-size: 14px;
}
}
/* Line height for readability (WCAG SC 1.4.8) */
p,
li,
dd,
dt {
line-height: 1.5;
max-width: 80ch; /* Optimal line length for readability */
}
h1, h2, h3, h4, h5, h6 {
line-height: 1.3;
margin-top: 1em;
margin-bottom: 0.5em;
}
/* Prevent text from being too small */
.small-thumbs h2,
.small-thumbs h3,
.text-sm,
small {
font-size: max(0.875rem, 14px);
}
/* ==================== TOUCH TARGETS ==================== */
/* Minimum 44x44px touch targets (WCAG SC 2.5.5) */
button,
a,
input[type="checkbox"],
input[type="radio"],
select,
.btn,
.touch-target,
[role="button"],
[role="link"] {
min-height: 44px;
min-width: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
}
/* Exception for text-only links in paragraphs */
p a,
li a {
min-height: auto;
min-width: auto;
display: inline;
padding: 2px 0;
}
/* Icon buttons */
.icon-button,
button[class*="icon-"],
a[class*="icon-"] {
min-height: 44px;
min-width: 44px;
padding: 10px;
}
/* ==================== SKIP LINKS ==================== */
.skip-links {
position: absolute;
top: -100px;
left: 0;
z-index: 10000;
background: var(--primary-color, #06a2cb);
}
.skip-links a {
position: absolute;
top: -100px;
left: 0;
padding: 12px 16px;
background: var(--primary-color, #06a2cb);
color: #ffffff;
font-weight: 600;
text-decoration: none;
border-radius: 0 0 4px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
}
.skip-links a:focus {
top: 0;
outline: 3px solid #ffffff;
outline-offset: 2px;
}
/* ==================== SCREEN READER ONLY ==================== */
.sr-only,
.visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border-width: 0 !important;
}
/* Allow screen reader text to be focusable when navigated to via keyboard */
.sr-only:focus,
.visually-hidden:focus {
position: static !important;
width: auto !important;
height: auto !important;
overflow: visible !important;
clip: auto !important;
white-space: normal !important;
}
/* ==================== FORM LABELS & INPUTS ==================== */
/* Ensure labels are visible and associated */
label {
display: block;
margin-bottom: 4px;
font-weight: 500;
color: var(--color-text-primary);
}
/* Required field indicator */
.required::after,
label.required::after {
content: " *";
color: var(--color-error, #ef4444);
font-weight: 600;
}
/* Error states */
input:invalid:not(:placeholder-shown),
.input-error,
.error {
border-color: var(--color-error, #ef4444) !important;
background: var(--color-error-bg, #fee2e2);
}
.error-message {
color: var(--color-error-text, #991b1b);
font-size: 0.875rem;
margin-top: 4px;
display: flex;
align-items: center;
gap: 4px;
}
.error-message::before {
content: "⚠";
font-size: 1rem;
}
/* Success states */
.input-success {
border-color: var(--color-success, #10b981) !important;
background: var(--color-success-bg, #d1fae5);
}
/* ==================== REDUCED MOTION ==================== */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
/* Keep essential animations but make them instant */
.spinner,
.loading,
[class*="animate"] {
animation: none !important;
}
}
/* ==================== HIGH CONTRAST MODE ==================== */
@media (prefers-contrast: high) {
* {
border-width: 2px !important;
}
button,
a,
input,
select,
textarea {
border: 2px solid currentColor !important;
}
:focus-visible {
outline-width: 4px !important;
outline-offset: 3px !important;
}
}
/* ==================== DARK MODE PREFERENCES ==================== */
@media (prefers-color-scheme: dark) {
/* Respect system preference if no theme is set */
body:not([data-theme]) {
background: #121212;
color: #f0f0f0;
}
}
/* ==================== TABLES ACCESSIBILITY ==================== */
table {
border-collapse: collapse;
width: 100%;
margin: 1rem 0;
}
th {
text-align: left;
font-weight: 600;
background: var(--color-bg-tertiary);
padding: 12px;
border: 1px solid var(--color-border-primary);
}
td {
padding: 12px;
border: 1px solid var(--color-border-primary);
}
/* Caption for table context */
caption {
font-weight: 600;
text-align: left;
padding: 8px 0;
caption-side: top;
}
/* ==================== IMAGES & MEDIA ==================== */
/* Ensure images don't overflow and have alt text */
img {
max-width: 100%;
height: auto;
}
/* Decorative images should have empty alt */
img[alt=""],
img:not([alt]) {
outline: 2px solid orange; /* Dev warning for missing alt */
}
/* Video controls must be accessible */
video:focus {
outline: 3px solid var(--focus-ring-color, #06a2cb);
outline-offset: 2px;
}
/* ==================== HEADINGS HIERARCHY ==================== */
/* Ensure proper heading hierarchy is visually clear */
h1 {
font-size: clamp(1.5rem, 5vw, 2.25rem);
font-weight: 700;
}
h2 {
font-size: clamp(1.25rem, 4vw, 1.875rem);
font-weight: 600;
}
h3 {
font-size: clamp(1.125rem, 3vw, 1.5rem);
font-weight: 600;
}
h4 {
font-size: clamp(1rem, 2.5vw, 1.25rem);
font-weight: 600;
}
h5, h6 {
font-size: 1rem;
font-weight: 600;
}
/* ==================== LISTS ==================== */
/* Ensure lists have proper spacing */
ul, ol {
padding-left: 1.5em;
}
li {
margin-bottom: 0.5em;
}
/* ==================== MODALS & OVERLAYS ==================== */
/* Modal focus trap indicator */
[role="dialog"]:focus,
.modal:focus {
outline: none;
}
/* Modal backdrop */
.modal-backdrop,
[role="dialog"]::backdrop {
background: rgba(0, 0, 0, 0.75);
}
/* Prevent background scroll when modal is open */
body.modal-open {
overflow: hidden;
}
/* ==================== LANGUAGE & TEXT DIRECTION ==================== */
/* Support for RTL languages */
[dir="rtl"] {
direction: rtl;
text-align: right;
}
[dir="rtl"] .sidebar {
left: auto;
right: 0;
}
/* ==================== STATUS MESSAGES ==================== */
/* Live regions for dynamic content */
[role="status"],
[role="alert"],
[aria-live] {
position: relative;
}
.status-message,
.alert {
padding: 12px 16px;
border-radius: 4px;
margin: 8px 0;
border-left: 4px solid;
}
.status-success {
background: var(--color-success-bg);
color: var(--color-success-text);
border-color: var(--color-success);
}
.status-error {
background: var(--color-error-bg);
color: var(--color-error-text);
border-color: var(--color-error);
}
.status-warning {
background: var(--color-warning-bg);
color: var(--color-warning-text);
border-color: var(--color-warning);
}
.status-info {
background: var(--color-info-bg);
color: var(--color-info-text);
border-color: var(--color-info);
}
/* ==================== PRINT STYLES ==================== */
@media print {
/* Remove unnecessary elements */
nav,
aside,
.no-print,
.sidebar,
header,
footer {
display: none !important;
}
/* Optimize for print */
* {
background: white !important;
color: black !important;
box-shadow: none !important;
text-shadow: none !important;
}
a {
text-decoration: underline;
}
/* Show URLs for links */
a[href]::after {
content: " (" attr(href) ")";
}
/* Prevent page breaks inside elements */
img,
pre,
blockquote,
table,
tr {
page-break-inside: avoid;
}
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,427 @@
/**
* EasyStream Design System
* Comprehensive design tokens and variables for consistent UI/UX
* Version: 2.0
*/
:root {
/* ==================== SPACING SYSTEM ==================== */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
--space-2xl: 48px;
--space-3xl: 64px;
/* ==================== TYPOGRAPHY ==================== */
--font-family-primary: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-family-mono: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace;
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-md: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 1.875rem; /* 30px */
--font-size-4xl: 2.25rem; /* 36px */
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
/* ==================== COLORS - NEUTRAL ==================== */
--color-white: #ffffff;
--color-black: #000000;
--color-gray-50: #f9fafb;
--color-gray-100: #f3f4f6;
--color-gray-200: #e5e7eb;
--color-gray-300: #d1d5db;
--color-gray-400: #9ca3af;
--color-gray-500: #6b7280;
--color-gray-600: #4b5563;
--color-gray-700: #374151;
--color-gray-800: #1f2937;
--color-gray-900: #111827;
/* ==================== SEMANTIC COLORS - LIGHT THEME ==================== */
--color-bg-primary: var(--color-white);
--color-bg-secondary: var(--color-gray-50);
--color-bg-tertiary: var(--color-gray-100);
--color-bg-elevated: var(--color-white);
--color-bg-overlay: rgba(0, 0, 0, 0.5);
--color-text-primary: var(--color-gray-900);
--color-text-secondary: var(--color-gray-600);
--color-text-tertiary: var(--color-gray-500);
--color-text-inverse: var(--color-white);
--color-text-disabled: var(--color-gray-400);
--color-border-primary: var(--color-gray-300);
--color-border-secondary: var(--color-gray-200);
--color-border-focus: var(--primary-color);
/* ==================== STATUS COLORS ==================== */
--color-success-bg: #d1fae5;
--color-success-text: #065f46;
--color-success-border: #34d399;
--color-success: #10b981;
--color-warning-bg: #fef3c7;
--color-warning-text: #92400e;
--color-warning-border: #fbbf24;
--color-warning: #f59e0b;
--color-error-bg: #fee2e2;
--color-error-text: #991b1b;
--color-error-border: #f87171;
--color-error: #ef4444;
--color-info-bg: #dbeafe;
--color-info-text: #1e40af;
--color-info-border: #60a5fa;
--color-info: #3b82f6;
/* ==================== BORDERS & RADIUS ==================== */
--border-width-thin: 1px;
--border-width-medium: 2px;
--border-width-thick: 4px;
--border-radius-none: 0;
--border-radius-sm: 4px;
--border-radius-md: 8px;
--border-radius-lg: 12px;
--border-radius-xl: 16px;
--border-radius-2xl: 24px;
--border-radius-full: 9999px;
/* ==================== SHADOWS ==================== */
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
--shadow-focus: 0 0 0 3px rgba(6, 162, 203, 0.5);
/* ==================== Z-INDEX ==================== */
--z-index-dropdown: 1000;
--z-index-sticky: 1020;
--z-index-fixed: 1030;
--z-index-modal-backdrop: 1040;
--z-index-modal: 1050;
--z-index-popover: 1060;
--z-index-tooltip: 1070;
--z-index-notification: 1080;
/* ==================== TRANSITIONS ==================== */
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-slowest: 500ms cubic-bezier(0.4, 0, 0.2, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
/* ==================== BREAKPOINTS (for reference in media queries) ==================== */
/* Mobile: 0-639px */
/* Tablet: 640-1023px */
/* Desktop: 1024-1279px */
/* Large Desktop: 1280px+ */
/* ==================== ACCESSIBILITY ==================== */
--focus-ring-width: 3px;
--focus-ring-offset: 2px;
--focus-ring-color: var(--color-border-focus);
--touch-target-min: 44px; /* Minimum touch target size for mobile */
/* ==================== ANIMATIONS ==================== */
--animation-duration-fast: 150ms;
--animation-duration-normal: 300ms;
--animation-duration-slow: 500ms;
}
/* ==================== DARK THEME OVERRIDES ==================== */
[data-theme*="dark"] {
--color-bg-primary: #121212;
--color-bg-secondary: #1c1c1c;
--color-bg-tertiary: #272727;
--color-bg-elevated: #1f1f1f;
--color-bg-overlay: rgba(0, 0, 0, 0.75);
--color-text-primary: #f0f0f0;
--color-text-secondary: #d0d0d0;
--color-text-tertiary: #aaa;
--color-text-inverse: #000;
--color-text-disabled: #666;
--color-border-primary: #2e2e2e;
--color-border-secondary: #232323;
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px -1px rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -2px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6), 0 4px 6px -4px rgba(0, 0, 0, 0.6);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.7), 0 8px 10px -6px rgba(0, 0, 0, 0.7);
--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.8);
--color-success-bg: #064e3b;
--color-success-text: #6ee7b7;
--color-warning-bg: #78350f;
--color-warning-text: #fcd34d;
--color-error-bg: #7f1d1d;
--color-error-text: #fca5a5;
--color-info-bg: #1e3a8a;
--color-info-text: #93c5fd;
}
/* ==================== UTILITY CLASSES ==================== */
/* Spacing utilities */
.m-0 { margin: 0; }
.m-xs { margin: var(--space-xs); }
.m-sm { margin: var(--space-sm); }
.m-md { margin: var(--space-md); }
.m-lg { margin: var(--space-lg); }
.m-xl { margin: var(--space-xl); }
.p-0 { padding: 0; }
.p-xs { padding: var(--space-xs); }
.p-sm { padding: var(--space-sm); }
.p-md { padding: var(--space-md); }
.p-lg { padding: var(--space-lg); }
.p-xl { padding: var(--space-xl); }
/* Typography utilities */
.text-xs { font-size: var(--font-size-xs); }
.text-sm { font-size: var(--font-size-sm); }
.text-md { font-size: var(--font-size-md); }
.text-lg { font-size: var(--font-size-lg); }
.text-xl { font-size: var(--font-size-xl); }
.font-light { font-weight: var(--font-weight-light); }
.font-normal { font-weight: var(--font-weight-normal); }
.font-medium { font-weight: var(--font-weight-medium); }
.font-semibold { font-weight: var(--font-weight-semibold); }
.font-bold { font-weight: var(--font-weight-bold); }
/* Border radius utilities */
.rounded-none { border-radius: var(--border-radius-none); }
.rounded-sm { border-radius: var(--border-radius-sm); }
.rounded-md { border-radius: var(--border-radius-md); }
.rounded-lg { border-radius: var(--border-radius-lg); }
.rounded-full { border-radius: var(--border-radius-full); }
/* Shadow utilities */
.shadow-xs { box-shadow: var(--shadow-xs); }
.shadow-sm { box-shadow: var(--shadow-sm); }
.shadow-md { box-shadow: var(--shadow-md); }
.shadow-lg { box-shadow: var(--shadow-lg); }
.shadow-xl { box-shadow: var(--shadow-xl); }
/* Transition utilities */
.transition-fast { transition: all var(--transition-fast); }
.transition-base { transition: all var(--transition-base); }
.transition-slow { transition: all var(--transition-slow); }
/* Accessibility utilities */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.focus-visible:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
border-radius: var(--border-radius-sm);
}
/* Skip to content link for keyboard navigation */
.skip-to-content {
position: absolute;
top: -40px;
left: 0;
background: var(--primary-color);
color: var(--color-white);
padding: var(--space-sm) var(--space-md);
text-decoration: none;
border-radius: var(--border-radius-sm);
z-index: var(--z-index-tooltip);
font-weight: var(--font-weight-semibold);
}
.skip-to-content:focus {
top: var(--space-sm);
left: var(--space-sm);
}
/* Touch target minimum size for mobile */
.touch-target {
min-height: var(--touch-target-min);
min-width: var(--touch-target-min);
display: inline-flex;
align-items: center;
justify-content: center;
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* High contrast mode support */
@media (prefers-contrast: high) {
:root {
--color-border-primary: #000;
--focus-ring-width: 4px;
}
[data-theme*="dark"] {
--color-border-primary: #fff;
}
}
/* ==================== COMPONENT PATTERNS ==================== */
/* Button Base */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--space-sm) var(--space-md);
font-size: var(--font-size-md);
font-weight: var(--font-weight-medium);
line-height: var(--line-height-normal);
border-radius: var(--border-radius-md);
border: var(--border-width-thin) solid transparent;
cursor: pointer;
transition: all var(--transition-base);
text-decoration: none;
min-height: var(--touch-target-min);
gap: var(--space-xs);
}
.btn:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background: var(--primary-color);
color: var(--color-white);
}
.btn-primary:hover:not(:disabled) {
background: var(--third-color);
}
.btn-secondary {
background: var(--color-bg-tertiary);
color: var(--color-text-primary);
border-color: var(--color-border-primary);
}
.btn-secondary:hover:not(:disabled) {
background: var(--color-bg-secondary);
}
/* Card Base */
.card {
background: var(--color-bg-elevated);
border: var(--border-width-thin) solid var(--color-border-secondary);
border-radius: var(--border-radius-lg);
padding: var(--space-lg);
box-shadow: var(--shadow-sm);
transition: box-shadow var(--transition-base);
}
.card:hover {
box-shadow: var(--shadow-md);
}
/* Input Base */
.input {
width: 100%;
padding: var(--space-sm) var(--space-md);
font-size: var(--font-size-md);
line-height: var(--line-height-normal);
color: var(--color-text-primary);
background: var(--color-bg-primary);
border: var(--border-width-thin) solid var(--color-border-primary);
border-radius: var(--border-radius-md);
transition: all var(--transition-base);
min-height: var(--touch-target-min);
}
.input:focus {
outline: none;
border-color: var(--color-border-focus);
box-shadow: var(--shadow-focus);
}
.input:disabled {
background: var(--color-bg-tertiary);
color: var(--color-text-disabled);
cursor: not-allowed;
}
/* Alert/Message Base */
.alert {
padding: var(--space-md);
border-radius: var(--border-radius-md);
border: var(--border-width-thin) solid;
margin-bottom: var(--space-md);
}
.alert-success {
background: var(--color-success-bg);
color: var(--color-success-text);
border-color: var(--color-success-border);
}
.alert-warning {
background: var(--color-warning-bg);
color: var(--color-warning-text);
border-color: var(--color-warning-border);
}
.alert-error {
background: var(--color-error-bg);
color: var(--color-error-text);
border-color: var(--color-error-border);
}
.alert-info {
background: var(--color-info-bg);
color: var(--color-info-text);
border-color: var(--color-info-border);
}

View File

@@ -0,0 +1,605 @@
/**
* EasyStream Responsive Design System
* Mobile-first responsive breakpoints and utilities
* Version: 1.0
*/
/* ==================== BREAKPOINT VARIABLES ==================== */
:root {
--breakpoint-xs: 0;
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;
/* Container max widths */
--container-sm: 640px;
--container-md: 768px;
--container-lg: 1024px;
--container-xl: 1280px;
--container-2xl: 1536px;
}
/* ==================== CONTAINER SYSTEM ==================== */
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
padding-left: var(--space-md, 16px);
padding-right: var(--space-md, 16px);
}
@media (min-width: 640px) {
.container {
max-width: var(--container-sm);
}
}
@media (min-width: 768px) {
.container {
max-width: var(--container-md);
}
}
@media (min-width: 1024px) {
.container {
max-width: var(--container-lg);
padding-left: var(--space-lg, 24px);
padding-right: var(--space-lg, 24px);
}
}
@media (min-width: 1280px) {
.container {
max-width: var(--container-xl);
}
}
@media (min-width: 1536px) {
.container {
max-width: var(--container-2xl);
}
}
/* Fluid container */
.container-fluid {
width: 100%;
padding-left: var(--space-md, 16px);
padding-right: var(--space-md, 16px);
}
/* ==================== GRID SYSTEM ==================== */
.grid {
display: grid;
gap: var(--space-md, 16px);
}
/* Auto-fit grid - responsive by default */
.grid-auto {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
/* 12 column grid */
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.grid-cols-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }
/* Column spans */
.col-span-1 { grid-column: span 1 / span 1; }
.col-span-2 { grid-column: span 2 / span 2; }
.col-span-3 { grid-column: span 3 / span 3; }
.col-span-4 { grid-column: span 4 / span 4; }
.col-span-6 { grid-column: span 6 / span 6; }
.col-span-12 { grid-column: span 12 / span 12; }
.col-span-full { grid-column: 1 / -1; }
/* Responsive grid columns */
@media (min-width: 640px) {
.sm\:grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.sm\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.sm\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (min-width: 768px) {
.md\:grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.md\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.md\:grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
.lg\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.lg\:grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.lg\:grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
/* ==================== FLEXBOX UTILITIES ==================== */
.flex {
display: flex;
}
.inline-flex {
display: inline-flex;
}
/* Flex direction */
.flex-row { flex-direction: row; }
.flex-col { flex-direction: column; }
.flex-row-reverse { flex-direction: row-reverse; }
.flex-col-reverse { flex-direction: column-reverse; }
/* Flex wrap */
.flex-wrap { flex-wrap: wrap; }
.flex-nowrap { flex-wrap: nowrap; }
/* Justify content */
.justify-start { justify-content: flex-start; }
.justify-end { justify-content: flex-end; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.justify-around { justify-content: space-around; }
.justify-evenly { justify-content: space-evenly; }
/* Align items */
.items-start { align-items: flex-start; }
.items-end { align-items: flex-end; }
.items-center { align-items: center; }
.items-baseline { align-items: baseline; }
.items-stretch { align-items: stretch; }
/* Gap utilities */
.gap-xs { gap: var(--space-xs, 4px); }
.gap-sm { gap: var(--space-sm, 8px); }
.gap-md { gap: var(--space-md, 16px); }
.gap-lg { gap: var(--space-lg, 24px); }
.gap-xl { gap: var(--space-xl, 32px); }
/* ==================== DISPLAY UTILITIES ==================== */
.hidden { display: none; }
.block { display: block; }
.inline { display: inline; }
.inline-block { display: inline-block; }
/* Responsive display */
@media (max-width: 639px) {
.xs\:hidden { display: none; }
.xs\:block { display: block; }
}
@media (min-width: 640px) {
.sm\:hidden { display: none; }
.sm\:block { display: block; }
.sm\:flex { display: flex; }
}
@media (min-width: 768px) {
.md\:hidden { display: none; }
.md\:block { display: block; }
.md\:flex { display: flex; }
}
@media (min-width: 1024px) {
.lg\:hidden { display: none; }
.lg\:block { display: block; }
.lg\:flex { display: flex; }
}
@media (min-width: 1280px) {
.xl\:hidden { display: none; }
.xl\:block { display: block; }
.xl\:flex { display: flex; }
}
/* ==================== MOBILE-FIRST VIDEO GRID ==================== */
/* Video thumbnail grid - responsive */
.video-grid,
.content-grid {
display: grid;
gap: var(--space-md, 16px);
grid-template-columns: 1fr; /* Mobile: 1 column */
}
@media (min-width: 640px) {
.video-grid,
.content-grid {
grid-template-columns: repeat(2, 1fr); /* Tablet: 2 columns */
}
}
@media (min-width: 768px) {
.video-grid,
.content-grid {
grid-template-columns: repeat(3, 1fr); /* Desktop: 3 columns */
}
}
@media (min-width: 1024px) {
.video-grid,
.content-grid {
grid-template-columns: repeat(4, 1fr); /* Large: 4 columns */
}
}
@media (min-width: 1280px) {
.video-grid,
.content-grid {
grid-template-columns: repeat(5, 1fr); /* XL: 5 columns */
}
}
@media (min-width: 1536px) {
.video-grid,
.content-grid {
grid-template-columns: repeat(6, 1fr); /* 2XL: 6 columns */
}
}
/* Compact video grid for sidebars */
.video-grid-compact {
display: grid;
gap: var(--space-sm, 8px);
grid-template-columns: 1fr;
}
/* ==================== RESPONSIVE TYPOGRAPHY ==================== */
/* Fluid typography using clamp */
.text-responsive-sm {
font-size: clamp(0.875rem, 2vw, 1rem);
}
.text-responsive-md {
font-size: clamp(1rem, 2.5vw, 1.125rem);
}
.text-responsive-lg {
font-size: clamp(1.125rem, 3vw, 1.5rem);
}
.text-responsive-xl {
font-size: clamp(1.5rem, 4vw, 2.25rem);
}
/* ==================== RESPONSIVE SPACING ==================== */
/* Responsive padding */
.p-responsive {
padding: var(--space-sm);
}
@media (min-width: 768px) {
.p-responsive {
padding: var(--space-md);
}
}
@media (min-width: 1024px) {
.p-responsive {
padding: var(--space-lg);
}
}
/* Responsive margin */
.m-responsive {
margin: var(--space-sm);
}
@media (min-width: 768px) {
.m-responsive {
margin: var(--space-md);
}
}
@media (min-width: 1024px) {
.m-responsive {
margin: var(--space-lg);
}
}
/* ==================== SIDEBAR LAYOUTS ==================== */
.layout-with-sidebar {
display: grid;
gap: var(--space-md);
grid-template-columns: 1fr; /* Mobile: stacked */
}
@media (min-width: 1024px) {
.layout-with-sidebar {
grid-template-columns: 250px 1fr; /* Desktop: sidebar + content */
}
.layout-with-sidebar.sidebar-right {
grid-template-columns: 1fr 300px;
}
}
/* Collapsible sidebar */
.sidebar {
width: 100%;
}
@media (min-width: 1024px) {
.sidebar.collapsed {
width: 64px;
overflow: hidden;
}
.sidebar.collapsed + .main-content {
margin-left: 64px;
}
}
/* ==================== TOUCH OPTIMIZATIONS ==================== */
/* Larger touch targets on mobile */
@media (max-width: 767px) {
button,
a,
.btn,
input[type="checkbox"],
input[type="radio"] {
min-height: 44px;
min-width: 44px;
}
/* Increase spacing between interactive elements */
.button-group > *,
.nav-menu > * {
margin: var(--space-sm) 0;
}
}
/* ==================== IMAGE RESPONSIVENESS ==================== */
img,
video,
iframe {
max-width: 100%;
height: auto;
}
.aspect-video {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.aspect-square {
aspect-ratio: 1 / 1;
overflow: hidden;
}
.aspect-video img,
.aspect-video video,
.aspect-square img,
.aspect-square video {
width: 100%;
height: 100%;
object-fit: cover;
}
/* ==================== TABLE RESPONSIVENESS ==================== */
.table-responsive {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
@media (max-width: 767px) {
/* Stack table on mobile */
.table-stack thead {
display: none;
}
.table-stack tr {
display: block;
margin-bottom: var(--space-md);
border: 1px solid var(--color-border-primary);
border-radius: var(--border-radius-md);
}
.table-stack td {
display: flex;
justify-content: space-between;
padding: var(--space-sm);
border-bottom: 1px solid var(--color-border-secondary);
}
.table-stack td:last-child {
border-bottom: none;
}
.table-stack td::before {
content: attr(data-label);
font-weight: 600;
margin-right: var(--space-sm);
}
}
/* ==================== MOBILE NAVIGATION ==================== */
/* Hamburger menu */
.mobile-menu-toggle {
display: block;
cursor: pointer;
padding: var(--space-sm);
}
@media (min-width: 768px) {
.mobile-menu-toggle {
display: none;
}
}
.mobile-menu {
position: fixed;
top: 0;
left: -100%;
width: 80%;
max-width: 300px;
height: 100vh;
background: var(--color-bg-primary);
box-shadow: var(--shadow-xl);
transition: left var(--transition-base);
z-index: var(--z-index-modal);
overflow-y: auto;
}
.mobile-menu.open {
left: 0;
}
.mobile-menu-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transition: opacity var(--transition-base), visibility var(--transition-base);
z-index: calc(var(--z-index-modal) - 1);
}
.mobile-menu-backdrop.open {
opacity: 1;
visibility: visible;
}
/* ==================== VIDEO PLAYER RESPONSIVENESS ==================== */
.video-player-wrapper {
position: relative;
width: 100%;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
height: 0;
overflow: hidden;
}
.video-player-wrapper iframe,
.video-player-wrapper video,
.video-player-wrapper .video-js {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* ==================== ORIENTATION SUPPORT ==================== */
@media (orientation: landscape) and (max-height: 500px) {
/* Optimize for landscape mobile */
.video-player-wrapper {
padding-bottom: 0;
height: 100vh;
}
header,
.sidebar {
display: none; /* Hide in fullscreen landscape */
}
}
/* ==================== RESPONSIVE UTILITIES ==================== */
/* Text alignment */
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
@media (min-width: 768px) {
.md\:text-left { text-align: left; }
.md\:text-center { text-align: center; }
.md\:text-right { text-align: right; }
}
/* Width utilities */
.w-full { width: 100%; }
.w-auto { width: auto; }
.w-screen { width: 100vw; }
.h-full { height: 100%; }
.h-auto { height: auto; }
.h-screen { height: 100vh; }
/* Max width utilities */
.max-w-xs { max-width: 320px; }
.max-w-sm { max-width: 384px; }
.max-w-md { max-width: 448px; }
.max-w-lg { max-width: 512px; }
.max-w-xl { max-width: 576px; }
.max-w-2xl { max-width: 672px; }
.max-w-full { max-width: 100%; }
/* ==================== PRINT RESPONSIVENESS ==================== */
@media print {
.no-print,
.mobile-menu,
.sidebar,
nav,
header,
footer {
display: none !important;
}
body {
font-size: 12pt;
}
.container {
max-width: 100%;
padding: 0;
}
}
/* ==================== LANDSCAPE MODE OPTIMIZATIONS ==================== */
@media (max-width: 1023px) and (orientation: landscape) {
/* Reduce vertical spacing in landscape */
.p-responsive {
padding-top: var(--space-sm);
padding-bottom: var(--space-sm);
}
/* Make navigation horizontal in landscape */
.mobile-menu {
width: 100%;
max-width: none;
height: auto;
max-height: 80vh;
}
}
/* ==================== SAFE AREAS (iPhone X+) ==================== */
@supports (padding: env(safe-area-inset-left)) {
.safe-area-padding {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}
header,
.mobile-menu {
padding-left: max(var(--space-md), env(safe-area-inset-left));
padding-right: max(var(--space-md), env(safe-area-inset-right));
}
}

View File

@@ -0,0 +1,514 @@
/**
* 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;
}