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:
515
f_scripts/shared/accessibility.css
Normal file
515
f_scripts/shared/accessibility.css
Normal 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;
|
||||
}
|
||||
}
|
||||
1
f_scripts/shared/datepicker/dist/date-range-picker.min.js.map
vendored
Normal file
1
f_scripts/shared/datepicker/dist/date-range-picker.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1
f_scripts/shared/datepicker/dist/tiny-date-picker.min.js.map
vendored
Normal file
1
f_scripts/shared/datepicker/dist/tiny-date-picker.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
427
f_scripts/shared/design-system.css
Normal file
427
f_scripts/shared/design-system.css
Normal 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);
|
||||
}
|
||||
605
f_scripts/shared/responsive.css
Normal file
605
f_scripts/shared/responsive.css
Normal 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));
|
||||
}
|
||||
}
|
||||
514
f_scripts/shared/theme-switcher.js
Normal file
514
f_scripts/shared/theme-switcher.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user