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

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;
}