3035 lines
116 KiB
Markdown
3035 lines
116 KiB
Markdown
¡Excelente! Basándome en el análisis UX detallado del video, aquí te presento una propuesta de código para HTML5, CSS3 y JavaScript que busca replicar los principios de diseño y experiencia de usuario observados en la interfaz de StreamYard.
|
|
|
|
Nos enfocaremos en:
|
|
* **Modularidad HTML:** Estructura clara y semántica.
|
|
* **Variables CSS:** Para mantener la consistencia en estilos y facilitar cambios.
|
|
* **Flexbox/Grid:** Para layouts complejos y responsivos.
|
|
* **JavaScript:** Para gestionar el estado de la UI, proporcionar feedback instantáneo, manejar modales, tabs y simular el drag-and-drop.
|
|
|
|
---
|
|
|
|
### **1. HTML5 (`index.html`)**
|
|
|
|
Esta estructura HTML refleja los principales paneles y componentes, con atributos `data-` para facilitar la interacción con JavaScript.
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>StreamYard UI - Propuesta UX</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
|
</head>
|
|
<body>
|
|
<div class="app-container">
|
|
<!-- Top Header Bar -->
|
|
<header class="top-header">
|
|
<div class="header-left">
|
|
<span class="app-title">Transmisión</span>
|
|
<span class="beta-tag">BETA</span>
|
|
</div>
|
|
<div class="header-right">
|
|
<button class="btn btn-secondary" data-action="edit-broadcast">Editor</button>
|
|
<div class="user-avatar">
|
|
<img src="https://via.placeholder.com/30" alt="User">
|
|
<i class="fas fa-caret-down"></i>
|
|
</div>
|
|
<button class="btn btn-primary live-button">
|
|
<i class="fas fa-video"></i> Transmitir en vivo
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Content Area -->
|
|
<main class="main-content">
|
|
<!-- Left Sidebar - Scenes -->
|
|
<aside class="sidebar-left">
|
|
<div class="sidebar-section">
|
|
<h3>Escenas <span class="beta-tag">BETA</span></h3>
|
|
</div>
|
|
<div class="sidebar-section scenes-list" id="scenes-list">
|
|
<h4>Mis Escenas</h4>
|
|
<!-- Scene items will be rendered here by JS -->
|
|
<div class="scene-item active" data-scene-id="1" draggable="true">
|
|
<img src="https://via.placeholder.com/120x67/333333/ffffff?text=Scene+1" alt="Scene Thumbnail">
|
|
<span class="scene-name">Demo scene 1</span>
|
|
<div class="scene-options">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
<div class="dropdown-menu">
|
|
<button data-action="show-on-stage">Mostrar en el escenario</button>
|
|
<button>Renombrar</button>
|
|
<button class="danger">Eliminar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="scene-item" data-scene-id="2" draggable="true">
|
|
<img src="https://via.placeholder.com/120x67/555555/ffffff?text=Scene+2" alt="Scene Thumbnail">
|
|
<span class="scene-name">Demo scene 2</span>
|
|
<div class="scene-options">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
<div class="dropdown-menu">
|
|
<button data-action="show-on-stage">Mostrar en el escenario</button>
|
|
<button>Renombrar</button>
|
|
<button class="danger">Eliminar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="btn btn-add-scene" data-action="add-scene">
|
|
<i class="fas fa-plus"></i> Nueva escena
|
|
</button>
|
|
</div>
|
|
<div class="sidebar-section actions-list">
|
|
<div class="action-item" data-action="intro-video">
|
|
<i class="fas fa-play-circle"></i> Establecer video de introducción
|
|
</div>
|
|
<div class="action-item" data-action="outro-video">
|
|
<i class="fas fa-stop-circle"></i> Establecer video de cierre
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Central Live Area -->
|
|
<section class="live-area">
|
|
<div class="video-player">
|
|
<div class="video-overlay">
|
|
<span class="quality">720p</span>
|
|
<span class="streamyard-logo">Producido con <img src="https://via.placeholder.com/80x20/ffffff/000000?text=StreamYard" alt="StreamYard"></span>
|
|
</div>
|
|
<img id="live-video-stream" src="https://via.placeholder.com/800x450/1e2430/ffffff?text=Vista+Previa+de+la+Transmision" alt="Live Video">
|
|
|
|
<!-- Dynamic elements like logo, overlay, background will be rendered here by JS -->
|
|
<img id="stage-overlay" class="stage-element" src="" alt="Superposición" style="display: none;">
|
|
<img id="stage-logo" class="stage-element" src="" alt="Logo" style="display: none;">
|
|
<div id="stage-background" class="stage-element" style="display: none;"></div>
|
|
|
|
<div id="participant-feed" class="participant-feed">
|
|
<!-- Participants will be dynamically added here -->
|
|
<div class="participant-name-tag" id="main-participant-tag" style="display: none;">Cesar Mendivil</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="participants-controls">
|
|
<div class="participants-grid" id="participants-grid">
|
|
<div class="participant-card active" data-participant-id="cesar">
|
|
<img src="https://via.placeholder.com/60/777777/ffffff?text=CM" alt="Cesar Mendivil">
|
|
<span>Cesar Mendivil</span>
|
|
</div>
|
|
<div class="participant-card add-card" data-action="add-participant">
|
|
<i class="fas fa-plus"></i>
|
|
<span>Invitar</span>
|
|
</div>
|
|
</div>
|
|
<div class="interaction-buttons">
|
|
<button class="btn-icon active" data-mic-toggle data-tooltip="Silenciar/Activar micrófono"><i class="fas fa-microphone"></i></button>
|
|
<button class="btn-icon active" data-cam-toggle data-tooltip="Encender/Apagar cámara"><i class="fas fa-video"></i></button>
|
|
<button class="btn-icon" data-action="share-screen" data-tooltip="Compartir pantalla"><i class="fas fa-desktop"></i></button>
|
|
<button class="btn-icon" data-action="open-settings" data-tooltip="Configuración"><i class="fas fa-cog"></i></button>
|
|
<button class="btn-presentar" data-action="present-or-invite">Presentar o invitar</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bottom-controls">
|
|
<button class="btn-icon" data-mic-toggle data-tooltip="Silenciar/Activar micrófono"><i class="fas fa-microphone"></i></button>
|
|
<button class="btn-icon" data-cam-toggle data-tooltip="Encender/Apagar cámara"><i class="fas fa-video"></i></button>
|
|
<button class="btn-icon" data-action="share-screen" data-tooltip="Compartir pantalla"><i class="fas fa-desktop"></i></sbutton>
|
|
<button class="btn-icon" data-action="open-settings" data-tooltip="Configuración"><i class="fas fa-cog"></i></button>
|
|
<button class="btn-icon" data-action="chat" data-tooltip="Chat del estudio"><i class="fas fa-comment-dots"></i></button>
|
|
<button class="btn-icon" data-action="help" data-tooltip="Ayuda"><i class="fas fa-question-circle"></i> ¿Necesitas ayuda?</button>
|
|
<button class="btn btn-danger leave-button" data-action="leave-studio" data-tooltip="Abandonar estudio"><i class="fas fa-phone-slash"></i></button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Right Sidebar - Settings -->
|
|
<aside class="sidebar-right">
|
|
<div class="brand-header">
|
|
<select class="dropdown-select" data-setting="brand-select">
|
|
<option value="brand1">Marca 1</option>
|
|
<option value="brand2">Marca 2</option>
|
|
</select>
|
|
<div class="brand-actions">
|
|
<i class="fas fa-pen" data-tooltip="Editar marca"></i>
|
|
<i class="fas fa-ellipsis-v" data-tooltip="Más opciones"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card settings-card">
|
|
<h4>Pack de Halloween <span class="new-tag">NUEVO</span></h4>
|
|
<p>6 overlays, 9 fondos</p>
|
|
<button class="btn btn-secondary">Añadir</button>
|
|
<i class="fas fa-trash-alt icon-action" data-tooltip="Eliminar pack"></i>
|
|
</div>
|
|
|
|
<div class="sidebar-tabs">
|
|
<div class="tab-item active" data-tab="estilo"><i class="fas fa-palette"></i> Estilo</div>
|
|
<div class="tab-item" data-tab="multimedia"><i class="fas fa-photo-video"></i> Activos multimedia</div>
|
|
</div>
|
|
|
|
<!-- Style Tab Content -->
|
|
<div class="tab-content active" id="estilo">
|
|
<div class="sidebar-section">
|
|
<h4>Ajustes preestablecidos <i class="fas fa-question-circle icon-help" data-tooltip="Selecciona un preset de estilo para la transmisión"></i></h4>
|
|
<div class="presets-grid" data-setting="presets">
|
|
<div class="preset-item active" data-preset="preset1"></div>
|
|
<div class="preset-item" data-preset="preset2"></div>
|
|
<div class="preset-item" data-preset="preset3"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h4>Color de la marca <i class="fas fa-question-circle icon-help" data-tooltip="Define el color principal de los elementos de tu marca"></i></h4>
|
|
<div class="color-picker">
|
|
<input type="color" value="#ff0040" class="color-input" data-setting="brand-color">
|
|
<span id="brand-color-value">#ff0040</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h4>Tema <i class="fas fa-question-circle icon-help" data-tooltip="Elige el estilo visual de los elementos en pantalla"></i></h4>
|
|
<div class="theme-buttons" data-setting="theme">
|
|
<button class="btn-theme" data-theme="bubble">Bubble</button>
|
|
<button class="btn-theme" data-theme="classic">Classic</button>
|
|
<button class="btn-theme" data-theme="minimal">Minimal</button>
|
|
<button class="btn-theme active" data-theme="block">Block</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<div class="toggle-switch-container">
|
|
<span>Mostrar nombres</span>
|
|
<label class="switch">
|
|
<input type="checkbox" checked data-setting="show-names">
|
|
<span class="slider round"></span>
|
|
</label>
|
|
</div>
|
|
<div class="toggle-switch-container">
|
|
<span>Mostrar títulos</span>
|
|
<label class="switch">
|
|
<input type="checkbox" data-setting="show-titles">
|
|
<span class="slider round"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Multimedia Tab Content -->
|
|
<div class="tab-content" id="multimedia">
|
|
<div class="sidebar-section">
|
|
<h4>Logo <i class="fas fa-question-circle icon-help" data-tooltip="Añade el logo de tu marca a la transmisión"></i></h4>
|
|
<div class="logo-uploader">
|
|
<div class="logo-preview" id="logo-preview">
|
|
<img src="https://via.placeholder.com/50/aaaaaa/ffffff?text=Logo" alt="Logo">
|
|
</div>
|
|
<button class="btn btn-secondary" data-action="upload-logo"><i class="fas fa-plus"></i></button>
|
|
<input type="file" id="logo-file-input" accept="image/*" style="display: none;">
|
|
</div>
|
|
<div class="logo-position" data-setting="logo-position">
|
|
<button class="btn-icon active" data-position="top-left" data-tooltip="Logo arriba izquierda"></button>
|
|
<button class="btn-icon" data-position="top-right" data-tooltip="Logo arriba derecha"></button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h4>Superposición <i class="fas fa-question-circle icon-help" data-tooltip="Añade gráficos de superposición a tu transmisión"></i></h4>
|
|
<div class="overlay-grid" data-setting="overlay">
|
|
<div class="overlay-item active" data-overlay="none" data-tooltip="Ninguna"></div>
|
|
<div class="overlay-item" data-overlay="streamyard-live" data-img="https://via.placeholder.com/100x56/ff0040/ffffff?text=EN+VIVO" data-tooltip="En Vivo con StreamYard"></div>
|
|
<div class="overlay-item add-more" data-action="open-overlay-modal"><i class="fas fa-plus"></i> Más</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h4>Código QR <i class="fas fa-question-circle icon-help" data-tooltip="Genera un código QR para compartir enlaces"></i></h4>
|
|
<div id="qr-code-generator">
|
|
<button class="btn btn-secondary" data-action="add-qr-code"><i class="fas fa-plus"></i> Nuevo código QR</button>
|
|
<div class="qr-code-form" style="display: none;">
|
|
<input type="text" placeholder="Título" data-qr-title>
|
|
<input type="text" placeholder="https://website.com" data-qr-url>
|
|
<button class="btn btn-primary" data-action="create-qr">Crear</button>
|
|
<button class="btn btn-secondary" data-action="cancel-qr">Cancelar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h4>Clips de video <i class="fas fa-question-circle icon-help" data-tooltip="Videos de introducción, conclusión o cuenta regresiva"></i></h4>
|
|
<div class="video-clips-grid">
|
|
<div class="video-clip-item" data-action="intro-video-clip" data-tooltip="Video de introducción">
|
|
<i class="fas fa-plus"></i> Video de introducción
|
|
<span class="premium-badge"></span>
|
|
</div>
|
|
<div class="video-clip-item" data-action="outro-video-clip" data-tooltip="Video de conclusión">
|
|
<i class="fas fa-plus"></i> Video de conclusión
|
|
<span class="premium-badge"></span>
|
|
</div>
|
|
</div>
|
|
<div class="toggle-switch-container">
|
|
<span>Bucle</span>
|
|
<label class="switch">
|
|
<input type="checkbox" data-setting="video-loop">
|
|
<span class="slider round"></span>
|
|
</label>
|
|
</div>
|
|
<div class="countdown-options">
|
|
<div class="countdown-item active" data-countdown="30">30</div>
|
|
<div class="countdown-item" data-countdown="60">60</div>
|
|
<div class="countdown-item add-more" data-action="add-custom-countdown"><i class="fas fa-plus"></i> Más</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h4>Fondo <i class="fas fa-question-circle icon-help" data-tooltip="Elige un fondo para tu transmisión"></i></h4>
|
|
<div class="background-grid" data-setting="background-video">
|
|
<div class="background-item active" data-background="none" data-tooltip="Ninguno"></div>
|
|
<div class="background-item" data-background="dark-bubbles" data-img="https://via.placeholder.com/100x56/111111/ffffff?text=Dark" data-tooltip="Burbujas oscuras"></div>
|
|
<div class="background-item" data-background="blue-waves" data-img="https://via.placeholder.com/100x56/007bff/ffffff?text=Waves" data-tooltip="Olas azules"></div>
|
|
<div class="background-item add-more" data-action="open-background-modal"><i class="fas fa-plus"></i> Más</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h4>Sonidos <i class="fas fa-question-circle icon-help" data-tooltip="Añade efectos de sonido a tu transmisión"></i></h4>
|
|
<div class="sound-volume-control">
|
|
<i class="fas fa-volume-up"></i>
|
|
<input type="range" min="0" max="100" value="70" class="slider-volume" data-setting="sound-volume">
|
|
<i class="fas fa-redo-alt" data-tooltip="Reestablecer volumen"></i>
|
|
</div>
|
|
<div class="sound-effects-grid" data-setting="sound-effects">
|
|
<button class="btn-theme" data-sound="applause">Aplausos Intensos</button>
|
|
<button class="btn-theme" data-sound="rebound">Rebote</button>
|
|
<button class="btn-theme" data-sound="heartbeat">Latido Rápido</button>
|
|
<button class="btn-theme" data-sound="bongos">Redoble de Bongos</button>
|
|
<button class="btn-theme" data-sound="gong">Gong</button>
|
|
<button class="btn-theme add-more" data-action="more-sounds"><i class="fas fa-plus"></i> Más sonidos</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h4>Música de fondo <i class="fas fa-question-circle icon-help" data-tooltip="Elige música de fondo para tu transmisión"></i></h4>
|
|
<div class="sound-volume-control">
|
|
<i class="fas fa-volume-up"></i>
|
|
<input type="range" min="0" max="100" value="50" class="slider-volume" data-setting="music-volume">
|
|
<i class="fas fa-redo-alt" data-tooltip="Reestablecer volumen"></i>
|
|
</div>
|
|
<div class="music-effects-grid" data-setting="music-effects">
|
|
<button class="btn-theme">Acoustic cinematic</button>
|
|
<button class="btn-theme">Dance pop</button>
|
|
<button class="btn-theme">Soñar despierto</button>
|
|
<button class="btn-theme">Alimientand o a los...</button>
|
|
<button class="btn-theme">En el espacio</button>
|
|
<button class="btn-theme">Lofi</button>
|
|
<button class="btn-theme">Manejo de noche</button>
|
|
<button class="btn-theme">Rock</button>
|
|
<button class="btn-theme add-more" data-action="more-music"><i class="fas fa-plus"></i> Más música</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</aside>
|
|
|
|
<!-- Far Right Vertical Bar -->
|
|
<aside class="sidebar-far-right">
|
|
<div class="far-right-item active" data-panel="comentarios" data-tooltip="Ver comentarios de espectadores"><i class="fas fa-comment-alt"></i> Comentarios</div>
|
|
<div class="far-right-item" data-panel="banners" data-tooltip="Gestionar banners de texto"><i class="fas fa-ad"></i> Banners</div>
|
|
<div class="far-right-item" data-panel="activos-multimedia" data-tooltip="Activos multimedia"><i class="fas fa-photo-video"></i> Activos multimedia</div>
|
|
<div class="far-right-item" data-panel="estilo" data-tooltip="Ajustar estilos visuales"><i class="fas fa-palette"></i> Estilo</div>
|
|
<div class="far-right-item" data-panel="notas" data-tooltip="Tomar notas durante la transmisión"><i class="fas fa-sticky-note"></i> Notas</div>
|
|
<div class="far-right-item" data-panel="personas" data-tooltip="Gestionar participantes"><i class="fas fa-users"></i> Personas</div>
|
|
<div class="far-right-item" data-panel="chat-privado" data-tooltip="Chat privado del estudio"><i class="fas fa-comments"></i> Chat privado</div>
|
|
</aside>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Modals -->
|
|
<div class="modal-backdrop" id="modal-backdrop"></div>
|
|
|
|
<!-- Configuration Modal (as seen in video) -->
|
|
<div class="modal" id="config-modal">
|
|
<div class="modal-header">
|
|
<h3>Configuración</h3>
|
|
<button class="close-modal" data-action="close-modal"><i class="fas fa-times"></i></button>
|
|
</div>
|
|
<div class="modal-content-flex">
|
|
<nav class="modal-nav">
|
|
<div class="modal-nav-item active" data-config-tab="general"><i class="fas fa-cog"></i> General</div>
|
|
<div class="modal-nav-item" data-config-tab="camera"><i class="fas fa-video"></i> Cámara</div>
|
|
<div class="modal-nav-item" data-config-tab="audio"><i class="fas fa-microphone"></i> Audio</div>
|
|
<div class="modal-nav-item" data-config-tab="visual-effects"><i class="fas fa-star"></i> Efectos visuales</div>
|
|
<div class="modal-nav-item" data-config-tab="recording"><i class="fas fa-record-vinyl"></i> Grabación</div>
|
|
<div class="modal-nav-item" data-config-tab="shortcuts"><i class="fas fa-keyboard"></i> Teclas de acceso rápido</div>
|
|
<div class="modal-nav-item" data-config-tab="designs"><i class="fas fa-th-large"></i> Diseños</div>
|
|
<div class="modal-nav-item" data-config-tab="guests"><i class="fas fa-users"></i> Invitados</div>
|
|
</nav>
|
|
<div class="modal-settings-content">
|
|
<!-- General Tab -->
|
|
<div class="config-tab-content active" id="general">
|
|
<h4>General</h4>
|
|
<div class="setting-row">
|
|
<span>Resolución</span>
|
|
<select>
|
|
<option>Alta definición (720p)</option>
|
|
<option>Full HD (1080p) - Premium</option>
|
|
</select>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span> Mostrar insignia de resolución en el escenario
|
|
</label>
|
|
<i class="fas fa-question-circle icon-help" data-tooltip="Muestra una pequeña etiqueta con la resolución de la transmisión."></i>
|
|
</div>
|
|
<div class="setting-row">
|
|
<span>Orientación</span>
|
|
<label class="radio-container">
|
|
<input type="radio" name="orientation" value="horizontal" checked>
|
|
<span class="radiomark"></span> Posición horizontal
|
|
</label>
|
|
<label class="radio-container">
|
|
<input type="radio" name="orientation" value="vertical">
|
|
<span class="radiomark"></span> Posición vertical
|
|
</label>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span> Mostrar mensajes informativos en el escenario
|
|
</label>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span> Mover los videos hacia arriba para que se vean los comentarios/banners
|
|
</label>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span> Avatares de audio
|
|
</label>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span> Agregar automáticamente medios presentados al escenario
|
|
</label>
|
|
</div>
|
|
<div class="setting-row">
|
|
<span>Apariencia</span>
|
|
<label class="radio-container">
|
|
<input type="radio" name="appearance" value="auto" checked>
|
|
<span class="radiomark"></span> Auto
|
|
</label>
|
|
<label class="radio-container">
|
|
<input type="radio" name="appearance" value="light">
|
|
<span class="radiomark"></span> Claro
|
|
</label>
|
|
<label class="radio-container">
|
|
<input type="radio" name="appearance" value="dark">
|
|
<span class="radiomark"></span> Oscuro
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<!-- Camera Tab -->
|
|
<div class="config-tab-content" id="camera">
|
|
<h4>Cámara</h4>
|
|
<div class="setting-row-img">
|
|
<img src="https://via.placeholder.com/200x112/555555/ffffff?text=Cámara" alt="Cámara">
|
|
<div class="camera-settings">
|
|
<div class="setting-row">
|
|
<span>Cámara</span>
|
|
<select>
|
|
<option>UVC Camera (tefe432i)</option>
|
|
<option>Webcam integrada</option>
|
|
</select>
|
|
</div>
|
|
<div class="setting-row">
|
|
<span>Resolución de la cámara</span>
|
|
<select>
|
|
<option>Definición estándar (480p)</option>
|
|
<option>Alta definición (720p)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span> Reflejar mi cámara
|
|
</label>
|
|
<i class="fas fa-question-circle icon-help" data-tooltip="Invierte la imagen de tu cámara para que te veas como en un espejo."></i>
|
|
</div>
|
|
<p class="small-text">Atención, si tienes una superposición que cubre gran parte de la pantalla, es posible que te cubras a ti mismo. <a href="#">Más información</a></p>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox">
|
|
<span class="checkmark"></span> (Avanzado) Modo de firewall restrictivo. Por favor, lea <a href="#">estas instrucciones</a> primero.
|
|
</label>
|
|
</div>
|
|
<p class="small-text-muted">Esta configuración no se puede cambiar mientras esté en el estudio.</p>
|
|
</div>
|
|
<!-- Audio Tab -->
|
|
<div class="config-tab-content" id="audio">
|
|
<h4>Audio</h4>
|
|
<div class="setting-row">
|
|
<span>Micrófono</span>
|
|
<select>
|
|
<option>Microphone Array (Realtek(R) Audio)</option>
|
|
<option>Auriculares</option>
|
|
</select>
|
|
<span class="audio-level"></span>
|
|
</div>
|
|
<div class="setting-row">
|
|
<span>Altavoz</span>
|
|
<select>
|
|
<option>Default - Headphones (Swiss Code Sec)</option>
|
|
<option>Altavoces internos</option>
|
|
</select>
|
|
<button class="btn btn-secondary">Prueba</button>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox">
|
|
<span class="checkmark"></span> Cancelación de eco
|
|
</label>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox">
|
|
<span class="checkmark"></span> Reducir ruido de fondo del micrófono
|
|
</label>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox">
|
|
<span class="checkmark"></span> Audio estéreo
|
|
</label>
|
|
</div>
|
|
<p class="small-text-muted indent">La cancelación de eco debe estar desactivada para usar audio estéreo</p>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span> Ajustar automáticamente el volumen del micrófono
|
|
</label>
|
|
<i class="fas fa-question-circle icon-help" data-tooltip="Ajusta el volumen del micrófono para que se escuche de manera óptima."></i>
|
|
</div>
|
|
</div>
|
|
<!-- Visual Effects Tab -->
|
|
<div class="config-tab-content" id="visual-effects">
|
|
<h4>Efectos visuales</h4>
|
|
<div class="alert-box">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<p>Nota: La función de pantalla verde utiliza un poco más de recursos informáticos que otras funciones. Cuanto más potente sea tu computadora, mejor funcionará.</p>
|
|
<button class="btn btn-primary">Entendido, ¡vamos a probarlo!</button>
|
|
</div>
|
|
</div>
|
|
<!-- Recording Tab -->
|
|
<div class="config-tab-content" id="recording">
|
|
<h4>Grabación</h4>
|
|
<div class="setting-row">
|
|
<span>Grabación local</span>
|
|
<label class="checkbox-container">
|
|
<input type="checkbox">
|
|
<span class="checkmark"></span> Grabaciones de audio y video individuales de alta calidad para cada invitado. <a href="#">Más información</a>
|
|
</label>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox">
|
|
<span class="checkmark"></span> Grabar de manera local para cada participante
|
|
</label>
|
|
</div>
|
|
<div class="info-box">
|
|
<i class="fas fa-info-circle"></i>
|
|
<p>Con el plan Gratuito, las transmisiones en vivo no se graban en StreamYard, pero sí se graban en las redes sociales. <a href="#">Pásate a un plan superior</a> para grabar tus transmisiones en vivo o crear una grabación.</p>
|
|
</div>
|
|
</div>
|
|
<!-- Shortcuts Tab -->
|
|
<div class="config-tab-content" id="shortcuts">
|
|
<h4>Teclas de acceso rápido</h4>
|
|
<div class="shortcuts-grid">
|
|
<div class="shortcut-item">
|
|
<span>Silenciar/activar el micrófono</span>
|
|
<input type="text" value="CTRL+D" readonly>
|
|
<i class="fas fa-times" data-tooltip="Eliminar atajo"></i>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<span>Encendido/apagado de la cámara</span>
|
|
<input type="text" value="CTRL+E" readonly>
|
|
<i class="fas fa-times" data-tooltip="Eliminar atajo"></i>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<span>Compartir pantalla</span>
|
|
<input type="text" value="SHIFT+S" readonly>
|
|
<i class="fas fa-times" data-tooltip="Eliminar atajo"></i>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<span>Compartir video</span>
|
|
<input type="text" value="SHIFT+V" readonly>
|
|
<i class="fas fa-times" data-tooltip="Eliminar atajo"></i>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<span>Compartir imagen</span>
|
|
<input type="text" value="SHIFT+I" readonly>
|
|
<i class="fas fa-times" data-tooltip="Eliminar atajo"></i>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<span>Reproducir/pausar video compartido</span>
|
|
<input type="text" value="No establecido" readonly>
|
|
<i class="fas fa-times" data-tooltip="Eliminar atajo"></i>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<span>Compartir la segunda cámara</span>
|
|
<input type="text" value="No establecido" readonly>
|
|
<i class="fas fa-times" data-tooltip="Eliminar atajo"></i>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<span>Siguiente diapositiva</span>
|
|
<input type="text" value="RIGHT" readonly>
|
|
<i class="fas fa-times" data-tooltip="Eliminar atajo"></i>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<span>Diapositiva anterior</span>
|
|
<input type="text" value="LEFT" readonly>
|
|
<i class="fas fa-times" data-tooltip="Eliminar atajo"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Designs Tab -->
|
|
<div class="config-tab-content" id="designs">
|
|
<h4>Diseños</h4>
|
|
<div class="setting-row">
|
|
<span>Selecciona qué diseños te gustaría usar</span>
|
|
</div>
|
|
<div class="design-checkbox-grid">
|
|
<label class="checkbox-container design-checkbox">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span>
|
|
<i class="fas fa-user-circle"></i> Diseño recortado
|
|
</label>
|
|
<label class="checkbox-container design-checkbox">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span>
|
|
<i class="fas fa-users"></i> Diseño de grupo
|
|
</label>
|
|
<label class="checkbox-container design-checkbox">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span>
|
|
<i class="fas fa-user"></i> Diseño de los focos
|
|
</label>
|
|
<label class="checkbox-container design-checkbox">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span>
|
|
<i class="fas fa-newspaper"></i> Diseño de noticias
|
|
</label>
|
|
<label class="checkbox-container design-checkbox">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span>
|
|
<i class="fas fa-tv"></i> Diseño de pantalla
|
|
</label>
|
|
<label class="checkbox-container design-checkbox">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span>
|
|
<i class="fas fa-images"></i> Diseño de imagen en imagen
|
|
</label>
|
|
<label class="checkbox-container design-checkbox">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span>
|
|
<i class="fas fa-film"></i> Diseño de cine
|
|
</label>
|
|
<label class="checkbox-container design-checkbox">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span>
|
|
<i class="fas fa-user"></i> Individual Central
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<!-- Guests Tab -->
|
|
<div class="config-tab-content" id="guests">
|
|
<h4>Invitados</h4>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span> Los invitados pueden transmitir esto a sus propios destinos
|
|
</label>
|
|
<i class="fas fa-question-circle icon-help" data-tooltip="Permite a tus invitados retransmitir tu estudio en sus propias plataformas."></i>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox" checked>
|
|
<span class="checkmark"></span> Reproducir un sonido cuando los invitados entren
|
|
</label>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox">
|
|
<span class="checkmark"></span> Los invitados deben autenticarse
|
|
</label>
|
|
</div>
|
|
<div class="setting-row">
|
|
<span>Invitados vetados</span>
|
|
<textarea placeholder="No hay invitados vetados"></textarea>
|
|
</div>
|
|
<div class="setting-row">
|
|
<label class="checkbox-container">
|
|
<input type="checkbox">
|
|
<span class="checkmark"></span> Sala verde
|
|
</label>
|
|
<i class="fas fa-question-circle icon-help" data-tooltip="Una sala de espera privada para tus invitados antes de salir en directo."></i>
|
|
</div>
|
|
<div class="info-box">
|
|
<i class="fas fa-info-circle"></i>
|
|
<p>Pásate al plan Teams o al plan de Negocio para tener acceso a la sala verde y asegurarte de que tus invitados tengan todo listo para la transmisión en vivo.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Background/Overlay Selection Modal -->
|
|
<div class="modal" id="media-selection-modal">
|
|
<div class="modal-header">
|
|
<h3 id="media-modal-title">Fondo</h3>
|
|
<button class="close-modal" data-action="close-modal"><i class="fas fa-times"></i></button>
|
|
</div>
|
|
<div class="modal-content">
|
|
<div class="upload-area">
|
|
<i class="fas fa-cloud-upload-alt"></i>
|
|
<p>Arrastra y suelta archivos y videos para subir</p>
|
|
<button class="btn btn-secondary">Agregar archivos</button>
|
|
<p class="small-text-muted">Tamaño recomendado: 1280 x 720 | Tamaño máximo del archivo de video: 200 MB</p>
|
|
<p class="small-text-muted">¿No quieres agregar uno propio? ¡Prueba uno de estos!</p>
|
|
</div>
|
|
<div class="media-tabs">
|
|
<button class="media-tab-item active" data-media-type="images">Imágenes</button>
|
|
<button class="media-tab-item" data-media-type="videos">Videos</button>
|
|
</div>
|
|
<div class="media-grid" id="media-grid">
|
|
<!-- Media items will be rendered here by JS -->
|
|
<div class="media-item" data-media-src="https://via.placeholder.com/100x56/007bff/ffffff?text=Blue+Waves" data-media-type="image">
|
|
<img src="https://via.placeholder.com/100x56/007bff/ffffff?text=Blue+Waves" alt="Olas Azules">
|
|
<span>Olas Azules</span>
|
|
</div>
|
|
<div class="media-item" data-media-src="https://via.placeholder.com/100x56/333333/ffffff?text=Dark+Mtns" data-media-type="image">
|
|
<img src="https://via.placeholder.com/100x56/333333/ffffff?text=Dark+Mtns" alt="Montañas Negras">
|
|
<span>Montañas Negras</span>
|
|
</div>
|
|
<div class="media-item" data-media-src="https://via.placeholder.com/100x56/a055ff/ffffff?text=Purple+Waves" data-media-type="image">
|
|
<img src="https://via.placeholder.com/100x56/a055ff/ffffff?text=Purple+Waves" alt="Olas Moradas">
|
|
<span>Olas Moradas</span>
|
|
</div>
|
|
<div class="media-item" data-media-src="https://via.placeholder.com/100x56/00ffff/ffffff?text=Cyan+Sky" data-media-type="image">
|
|
<img src="https://via.placeholder.com/100x56/00ffff/ffffff?text=Cyan+Sky" alt="Cielo Cian">
|
|
<span>Cielo Cian</span>
|
|
</div>
|
|
<div class="media-item" data-media-src="https://via.placeholder.com/100x56/00aaff/ffffff?text=Blue+Pattern" data-media-type="image">
|
|
<img src="https://via.placeholder.com/100x56/00aaff/ffffff?text=Blue+Pattern" alt="Olas Cian">
|
|
<span>Olas Cian</span>
|
|
</div>
|
|
<div class="media-item" data-media-src="https://via.placeholder.com/100x56/ff00cc/ffffff?text=Gradient" data-media-type="image">
|
|
<img src="https://via.placeholder.com/100x56/ff00cc/ffffff?text=Gradient" alt="Gradiente Azul Rosa">
|
|
<span>Gradiente Azul Rosa</span>
|
|
</div>
|
|
<!-- Example video media items -->
|
|
<div class="media-item" data-media-src="https://via.placeholder.com/100x56/0088ff/ffffff?text=Ocean+Blue" data-media-type="video">
|
|
<img src="https://via.placeholder.com/100x56/0088ff/ffffff?text=Ocean+Blue" alt="Océano azul">
|
|
<span>Océano azul</span>
|
|
</div>
|
|
<div class="media-item" data-media-src="https://via.placeholder.com/100x56/00cc00/ffffff?text=Green+Waves" data-media-type="video">
|
|
<img src="https://via.placeholder.com/100x56/00cc00/ffffff?text=Green+Waves" alt="Olas verdes">
|
|
<span>Olas verdes</span>
|
|
</div>
|
|
<div class="media-item" data-media-src="https://via.placeholder.com/100x56/ff0088/ffffff?text=Pink+Waves" data-media-type="video">
|
|
<img src="https://via.placeholder.com/100x56/ff0088/ffffff?text=Pink+Waves" alt="Olas rosadas">
|
|
<span>Olas rosadas</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<script src="script.js"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### **2. CSS3 (`style.css`)**
|
|
|
|
Manteniendo las variables y un diseño moderno, se añaden estilos para los nuevos componentes como modales y tooltips.
|
|
|
|
```css
|
|
/* Variables CSS para consistencia (Reiterado para contexto completo) */
|
|
:root {
|
|
--primary-color: #ff0040; /* Rojo/Rosa distintivo */
|
|
--secondary-color: #3f475a; /* Gris oscuro para botones secundarios */
|
|
--background-dark: #1e2430; /* Fondo principal oscuro */
|
|
--background-medium: #2a313e; /* Fondos de tarjetas y secciones */
|
|
--text-light: #e0e6f0; /* Texto claro */
|
|
--text-muted: #8c96a3; /* Texto secundario/desactivado */
|
|
--border-color: #3a4459; /* Color de borde sutil */
|
|
--shadow-light: rgba(0, 0, 0, 0.1);
|
|
--spacing-xs: 0.5rem;
|
|
--spacing-sm: 0.75rem;
|
|
--spacing-md: 1rem;
|
|
--spacing-lg: 1.5rem;
|
|
--radius-sm: 4px;
|
|
--radius-md: 8px;
|
|
--radius-lg: 12px;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
margin: 0;
|
|
padding: 0;
|
|
background-color: var(--background-dark);
|
|
color: var(--text-light);
|
|
overflow: hidden; /* Evita scroll en el body principal */
|
|
font-size: 14px; /* Ajuste base para legibilidad */
|
|
}
|
|
|
|
.app-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
}
|
|
|
|
/* --- Top Header Bar --- */
|
|
.top-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--spacing-md) var(--spacing-lg);
|
|
background-color: var(--background-medium);
|
|
border-bottom: 1px solid var(--border-color);
|
|
box-shadow: 0 2px 4px var(--shadow-light);
|
|
z-index: 100;
|
|
}
|
|
|
|
.header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.app-title {
|
|
font-weight: 600;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.beta-tag {
|
|
background-color: rgba(var(--primary-color), 0.2);
|
|
color: var(--primary-color);
|
|
padding: 2px 8px;
|
|
border-radius: var(--radius-sm);
|
|
font-size: 0.7rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-md);
|
|
}
|
|
|
|
.user-avatar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.user-avatar img {
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
border: 1px solid var(--border-color);
|
|
}
|
|
|
|
.live-button {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
/* --- Main Content Area --- */
|
|
.main-content {
|
|
display: flex;
|
|
flex: 1; /* Ocupa el espacio restante */
|
|
overflow: hidden; /* Oculta el desbordamiento dentro del main */
|
|
}
|
|
|
|
/* --- Left Sidebar --- */
|
|
.sidebar-left {
|
|
width: 250px;
|
|
background-color: var(--background-dark);
|
|
border-right: 1px solid var(--border-color);
|
|
padding: var(--spacing-md) 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow-y: auto; /* Permite scroll si el contenido es largo */
|
|
flex-shrink: 0; /* No se encoge */
|
|
}
|
|
|
|
.sidebar-section {
|
|
padding: var(--spacing-md) var(--spacing-lg);
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.sidebar-section:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.sidebar-left h3 {
|
|
margin-top: 0;
|
|
margin-bottom: var(--spacing-sm);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.sidebar-left h4 {
|
|
color: var(--text-muted);
|
|
font-size: 0.9rem;
|
|
margin-top: 0;
|
|
margin-bottom: var(--spacing-sm);
|
|
}
|
|
|
|
.scenes-list {
|
|
flex-grow: 1; /* Permite que la lista de escenas crezca */
|
|
}
|
|
|
|
.scene-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
padding: var(--spacing-xs) var(--spacing-sm);
|
|
margin-bottom: var(--spacing-sm);
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
transition: background-color 0.2s ease;
|
|
position: relative; /* Para el menú de opciones */
|
|
}
|
|
|
|
.scene-item:hover {
|
|
background-color: var(--background-medium);
|
|
}
|
|
|
|
.scene-item.active {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
}
|
|
|
|
.scene-item.active .scene-name {
|
|
color: white;
|
|
}
|
|
|
|
.scene-item img {
|
|
width: 60px; /* Reducido para mejor ajuste */
|
|
height: 34px; /* Proporcional a 16:9 */
|
|
border-radius: var(--radius-sm);
|
|
object-fit: cover;
|
|
border: 1px solid var(--border-color);
|
|
}
|
|
|
|
.scene-item .scene-name {
|
|
flex-grow: 1;
|
|
color: var(--text-light);
|
|
}
|
|
|
|
.scene-item .scene-options {
|
|
position: relative;
|
|
padding: 0 5px;
|
|
}
|
|
|
|
.scene-item .scene-options i {
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.scene-item .scene-options .dropdown-menu {
|
|
display: none;
|
|
position: absolute;
|
|
top: 100%;
|
|
right: 0;
|
|
background-color: var(--background-medium);
|
|
border-radius: var(--radius-sm);
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
|
min-width: 150px;
|
|
z-index: 10;
|
|
}
|
|
.scene-item .scene-options .dropdown-menu.active {
|
|
display: block;
|
|
}
|
|
.scene-item .scene-options .dropdown-menu button {
|
|
display: block;
|
|
width: 100%;
|
|
text-align: left;
|
|
padding: 8px 12px;
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-light);
|
|
cursor: pointer;
|
|
}
|
|
.scene-item .scene-options .dropdown-menu button:hover {
|
|
background-color: var(--secondary-color);
|
|
}
|
|
.scene-item .scene-options .dropdown-menu button.danger {
|
|
color: #dc3545;
|
|
}
|
|
.scene-item .scene-options .dropdown-menu button.danger:hover {
|
|
background-color: rgba(220, 53, 69, 0.2);
|
|
}
|
|
|
|
|
|
.btn-add-scene {
|
|
width: 100%;
|
|
margin-top: var(--spacing-md);
|
|
background-color: var(--secondary-color);
|
|
border-color: var(--secondary-color);
|
|
color: var(--text-light);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.action-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
padding: var(--spacing-sm) 0;
|
|
color: var(--text-light);
|
|
cursor: pointer;
|
|
transition: color 0.2s ease;
|
|
}
|
|
|
|
.action-item:hover {
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
/* --- Central Live Area --- */
|
|
.live-area {
|
|
flex: 1; /* Ocupa todo el espacio central disponible */
|
|
background-color: #000; /* Fondo para el video */
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--spacing-md);
|
|
gap: var(--spacing-md);
|
|
overflow-y: auto; /* Permite scroll si el contenido es demasiado alto */
|
|
}
|
|
|
|
.video-player {
|
|
position: relative;
|
|
width: 100%;
|
|
max-width: 900px; /* Max width for the video player */
|
|
aspect-ratio: 16 / 9; /* Standard video aspect ratio */
|
|
background-color: var(--background-dark); /* Color de fondo mientras no hay video */
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
margin-bottom: var(--spacing-md);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.video-player img#live-video-stream {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
}
|
|
|
|
.video-overlay {
|
|
position: absolute;
|
|
top: var(--spacing-sm);
|
|
left: var(--spacing-sm);
|
|
right: var(--spacing-sm);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
color: white;
|
|
font-size: 0.8rem;
|
|
z-index: 10;
|
|
}
|
|
|
|
.streamyard-logo {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
}
|
|
|
|
.streamyard-logo img {
|
|
height: 15px;
|
|
width: auto;
|
|
}
|
|
|
|
.stage-element {
|
|
position: absolute;
|
|
z-index: 5; /* Asegurar que estén sobre el video */
|
|
}
|
|
|
|
#stage-overlay {
|
|
top: var(--spacing-sm);
|
|
left: var(--spacing-sm);
|
|
width: 200px; /* Ejemplo de tamaño */
|
|
height: auto;
|
|
object-fit: contain;
|
|
}
|
|
|
|
#stage-logo {
|
|
top: var(--spacing-sm);
|
|
right: var(--spacing-sm);
|
|
width: 80px; /* Ejemplo de tamaño */
|
|
height: auto;
|
|
object-fit: contain;
|
|
}
|
|
|
|
#stage-background {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-size: cover;
|
|
background-position: center;
|
|
z-index: 1;
|
|
}
|
|
|
|
|
|
.participant-feed {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%; /* Ocupa todo el espacio para posicionar */
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: flex-end; /* Posicionar los elementos de participantes */
|
|
z-index: 5;
|
|
}
|
|
|
|
.participant-name-tag {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
padding: 5px 10px;
|
|
border-radius: var(--radius-sm);
|
|
font-size: 0.85rem;
|
|
margin-bottom: 20px; /* Ejemplo de posición */
|
|
}
|
|
|
|
|
|
.participants-controls {
|
|
width: 100%;
|
|
max-width: 900px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: var(--spacing-md);
|
|
background-color: var(--background-medium);
|
|
padding: var(--spacing-md);
|
|
border-radius: var(--radius-lg);
|
|
}
|
|
|
|
.participants-grid {
|
|
display: flex;
|
|
gap: var(--spacing-md);
|
|
justify-content: center;
|
|
flex-wrap: wrap; /* Permite que los participantes se envuelvan */
|
|
}
|
|
|
|
.participant-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
background-color: var(--background-dark);
|
|
padding: var(--spacing-sm);
|
|
border-radius: var(--radius-md);
|
|
border: 2px solid transparent;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.participant-card.active {
|
|
border-color: var(--primary-color);
|
|
}
|
|
|
|
.participant-card img {
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
margin-bottom: var(--spacing-xs);
|
|
}
|
|
|
|
.participant-card span {
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.participant-card.add-card {
|
|
justify-content: center;
|
|
font-size: 1.5rem;
|
|
color: var(--text-muted);
|
|
border: 2px dashed var(--border-color);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.participant-card.add-card i {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.participant-card.add-card:hover {
|
|
border-color: var(--primary-color);
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.interaction-buttons {
|
|
display: flex;
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-sm);
|
|
}
|
|
|
|
.btn-presentar {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
padding: var(--spacing-xs) var(--spacing-md);
|
|
border: none;
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
.btn-presentar:hover {
|
|
background-color: #e00038;
|
|
}
|
|
|
|
.bottom-controls {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: var(--spacing-md);
|
|
background-color: var(--background-medium);
|
|
padding: var(--spacing-md) var(--spacing-lg);
|
|
border-radius: var(--radius-lg);
|
|
margin-top: var(--spacing-md);
|
|
}
|
|
|
|
.leave-button {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
border-color: var(--primary-color);
|
|
}
|
|
.leave-button:hover {
|
|
background-color: #e00038;
|
|
}
|
|
|
|
|
|
/* --- Right Sidebar - Settings --- */
|
|
.sidebar-right {
|
|
width: 320px; /* Ancho más amplio para los ajustes */
|
|
background-color: var(--background-dark);
|
|
border-left: 1px solid var(--border-color);
|
|
padding: var(--spacing-md) 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow-y: auto;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.brand-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--spacing-sm) var(--spacing-lg);
|
|
border-bottom: 1px solid var(--border-color);
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
|
|
.brand-actions {
|
|
display: flex;
|
|
gap: var(--spacing-md);
|
|
color: var(--text-muted);
|
|
}
|
|
.brand-actions i {
|
|
cursor: pointer;
|
|
}
|
|
.brand-actions i:hover {
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.dropdown-select {
|
|
background-color: var(--background-medium);
|
|
color: var(--text-light);
|
|
border: 1px solid var(--border-color);
|
|
padding: var(--spacing-xs) var(--spacing-sm);
|
|
border-radius: var(--radius-sm);
|
|
appearance: none; /* Elimina estilos nativos del select */
|
|
-webkit-appearance: none;
|
|
-moz-appearance: none;
|
|
cursor: pointer;
|
|
}
|
|
.dropdown-select:focus {
|
|
outline: none;
|
|
border-color: var(--primary-color);
|
|
}
|
|
|
|
.card {
|
|
background-color: var(--background-medium);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--spacing-md);
|
|
margin: 0 var(--spacing-md) var(--spacing-md);
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
position: relative;
|
|
}
|
|
|
|
.settings-card h4 {
|
|
margin-top: 0;
|
|
margin-bottom: var(--spacing-xs);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.new-tag, .premium-badge {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: var(--radius-sm);
|
|
font-size: 0.7rem;
|
|
font-weight: 500;
|
|
}
|
|
.premium-badge::before {
|
|
content: "Premium";
|
|
}
|
|
|
|
|
|
.settings-card p {
|
|
color: var(--text-muted);
|
|
font-size: 0.85rem;
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
|
|
.settings-card .btn-secondary {
|
|
padding: 8px 16px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.settings-card .icon-action {
|
|
position: absolute;
|
|
top: var(--spacing-md);
|
|
right: var(--spacing-md);
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
}
|
|
.settings-card .icon-action:hover {
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.sidebar-tabs {
|
|
display: flex;
|
|
justify-content: space-around;
|
|
padding: 0 var(--spacing-md);
|
|
border-bottom: 1px solid var(--border-color);
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
|
|
.tab-item {
|
|
flex: 1;
|
|
text-align: center;
|
|
padding: var(--spacing-sm) 0;
|
|
cursor: pointer;
|
|
color: var(--text-muted);
|
|
border-bottom: 2px solid transparent;
|
|
transition: all 0.2s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.tab-item:hover {
|
|
color: var(--text-light);
|
|
}
|
|
|
|
.tab-item.active {
|
|
color: var(--primary-color);
|
|
border-bottom-color: var(--primary-color);
|
|
}
|
|
|
|
.tab-content {
|
|
display: none; /* Por defecto oculto */
|
|
padding: 0 var(--spacing-lg);
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block; /* Muestra el activo */
|
|
}
|
|
|
|
.icon-help {
|
|
color: var(--text-muted);
|
|
font-size: 0.8rem;
|
|
margin-left: var(--spacing-xs);
|
|
cursor: help;
|
|
}
|
|
|
|
.presets-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-sm);
|
|
}
|
|
|
|
.preset-item {
|
|
aspect-ratio: 1 / 1;
|
|
background-color: var(--background-medium);
|
|
border: 2px solid var(--border-color);
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
transition: border-color 0.2s ease;
|
|
}
|
|
|
|
.preset-item:hover, .preset-item.active {
|
|
border-color: var(--primary-color);
|
|
}
|
|
|
|
.color-picker {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-sm);
|
|
}
|
|
|
|
.color-input {
|
|
-webkit-appearance: none;
|
|
-moz-appearance: none;
|
|
appearance: none;
|
|
width: 30px;
|
|
height: 30px;
|
|
background-color: transparent;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
.color-input::-webkit-color-swatch {
|
|
border-radius: 50%;
|
|
border: 2px solid var(--border-color);
|
|
}
|
|
.color-input::-moz-color-swatch {
|
|
border-radius: 50%;
|
|
border: 2px solid var(--border-color);
|
|
}
|
|
|
|
.theme-buttons {
|
|
display: flex;
|
|
gap: var(--spacing-xs);
|
|
margin-top: var(--spacing-sm);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.btn-theme {
|
|
background-color: var(--background-medium);
|
|
color: var(--text-light);
|
|
border: 1px solid var(--border-color);
|
|
padding: 8px 12px;
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.btn-theme:hover, .btn-theme.active {
|
|
background-color: var(--primary-color);
|
|
border-color: var(--primary-color);
|
|
color: white;
|
|
}
|
|
|
|
/* Toggle Switch */
|
|
.toggle-switch-container {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-top: var(--spacing-md);
|
|
}
|
|
|
|
.switch {
|
|
position: relative;
|
|
display: inline-block;
|
|
width: 40px;
|
|
height: 20px;
|
|
}
|
|
|
|
.switch input {
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.slider {
|
|
position: absolute;
|
|
cursor: pointer;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: var(--secondary-color);
|
|
transition: 0.4s;
|
|
border-radius: 20px;
|
|
}
|
|
|
|
.slider:before {
|
|
position: absolute;
|
|
content: "";
|
|
height: 16px;
|
|
width: 16px;
|
|
left: 2px;
|
|
bottom: 2px;
|
|
background-color: white;
|
|
transition: 0.4s;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
input:checked + .slider {
|
|
background-color: var(--primary-color);
|
|
}
|
|
|
|
input:focus + .slider {
|
|
box-shadow: 0 0 1px var(--primary-color);
|
|
}
|
|
|
|
input:checked + .slider:before {
|
|
transform: translateX(20px);
|
|
}
|
|
|
|
/* Multimedia Tab Specifics */
|
|
.logo-uploader {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-md);
|
|
margin-top: var(--spacing-sm);
|
|
}
|
|
.logo-preview {
|
|
width: 60px;
|
|
height: 60px;
|
|
background-color: var(--background-dark);
|
|
border: 2px dashed var(--border-color);
|
|
border-radius: var(--radius-md);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overflow: hidden;
|
|
}
|
|
.logo-preview img {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
.logo-position {
|
|
display: flex;
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-md);
|
|
}
|
|
.logo-position .btn-icon {
|
|
width: 40px;
|
|
height: 40px;
|
|
background-color: var(--background-medium);
|
|
border: 1px solid var(--border-color);
|
|
color: var(--text-light);
|
|
border-radius: var(--radius-sm);
|
|
font-size: 0.9rem;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
position: relative;
|
|
}
|
|
.logo-position .btn-icon[data-position="top-left"]::before { content: "\f02d"; font-family: 'Font Awesome 5 Free'; font-weight: 900; position: absolute; top: 5px; left: 5px; }
|
|
.logo-position .btn-icon[data-position="top-right"]::before { content: "\f02d"; font-family: 'Font Awesome 5 Free'; font-weight: 900; position: absolute; top: 5px; right: 5px; }
|
|
|
|
|
|
.logo-position .btn-icon.active {
|
|
background-color: var(--primary-color);
|
|
border-color: var(--primary-color);
|
|
}
|
|
|
|
.overlay-grid, .background-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-sm);
|
|
}
|
|
.overlay-item, .background-item {
|
|
aspect-ratio: 16/9;
|
|
background-color: var(--background-medium);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.overlay-item.active, .background-item.active {
|
|
border-color: var(--primary-color);
|
|
}
|
|
.overlay-item:hover, .background-item:hover {
|
|
border-color: var(--primary-color);
|
|
}
|
|
.overlay-item.add-more, .background-item.add-more {
|
|
font-size: 1.2rem;
|
|
color: var(--text-muted);
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
border-style: dashed;
|
|
}
|
|
.overlay-item.add-more i, .background-item.add-more i {
|
|
font-size: 1.5rem;
|
|
color: var(--primary-color);
|
|
}
|
|
.overlay-item img, .background-item img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
#qr-code-generator .qr-code-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-sm);
|
|
}
|
|
#qr-code-generator input {
|
|
background-color: var(--background-medium);
|
|
border: 1px solid var(--border-color);
|
|
padding: var(--spacing-xs) var(--spacing-sm);
|
|
border-radius: var(--radius-sm);
|
|
color: var(--text-light);
|
|
}
|
|
#qr-code-generator input::placeholder {
|
|
color: var(--text-muted);
|
|
}
|
|
#qr-code-generator button {
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
|
|
.video-clips-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-sm);
|
|
}
|
|
.video-clip-item {
|
|
background-color: var(--background-medium);
|
|
border: 1px dashed var(--border-color);
|
|
border-radius: var(--radius-md);
|
|
padding: var(--spacing-md);
|
|
text-align: center;
|
|
font-size: 0.9rem;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
position: relative;
|
|
}
|
|
.video-clip-item i {
|
|
font-size: 1.2rem;
|
|
color: var(--primary-color);
|
|
}
|
|
.video-clip-item:hover {
|
|
border-color: var(--primary-color);
|
|
color: var(--primary-color);
|
|
}
|
|
.video-clip-item .premium-badge {
|
|
position: absolute;
|
|
top: 5px;
|
|
right: 5px;
|
|
font-size: 0.6rem;
|
|
padding: 2px 5px;
|
|
background-color: #f7b000; /* Color premium */
|
|
}
|
|
|
|
.countdown-options {
|
|
display: flex;
|
|
gap: var(--spacing-xs);
|
|
margin-top: var(--spacing-md);
|
|
}
|
|
.countdown-item {
|
|
background-color: var(--background-medium);
|
|
border: 1px solid var(--border-color);
|
|
padding: 8px 12px;
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
font-size: 0.9rem;
|
|
}
|
|
.countdown-item.active {
|
|
background-color: var(--primary-color);
|
|
border-color: var(--primary-color);
|
|
color: white;
|
|
}
|
|
.countdown-item:hover {
|
|
background-color: var(--secondary-color);
|
|
border-color: var(--secondary-color);
|
|
}
|
|
.countdown-item.add-more {
|
|
border-style: dashed;
|
|
color: var(--primary-color);
|
|
}
|
|
.countdown-item.add-more i {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
|
|
.sound-volume-control {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-sm);
|
|
}
|
|
.slider-volume {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 100%;
|
|
height: 5px;
|
|
background: var(--secondary-color);
|
|
outline: none;
|
|
opacity: 0.7;
|
|
transition: opacity .2s;
|
|
border-radius: 5px;
|
|
}
|
|
.slider-volume:hover {
|
|
opacity: 1;
|
|
}
|
|
.slider-volume::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 15px;
|
|
height: 15px;
|
|
border-radius: 50%;
|
|
background: var(--primary-color);
|
|
cursor: pointer;
|
|
}
|
|
.slider-volume::-moz-range-thumb {
|
|
width: 15px;
|
|
height: 15px;
|
|
border-radius: 50%;
|
|
background: var(--primary-color);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.sound-effects-grid, .music-effects-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: var(--spacing-xs);
|
|
margin-top: var(--spacing-sm);
|
|
}
|
|
|
|
/* --- Far Right Vertical Bar --- */
|
|
.sidebar-far-right {
|
|
width: 60px; /* Ancho fijo para la barra vertical de íconos */
|
|
background-color: var(--background-medium);
|
|
border-left: 1px solid var(--border-color);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: var(--spacing-lg) 0;
|
|
gap: var(--spacing-lg);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.far-right-item {
|
|
color: var(--text-muted);
|
|
font-size: 0.8rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 4px;
|
|
cursor: pointer;
|
|
transition: color 0.2s ease;
|
|
width: 100%;
|
|
padding: var(--spacing-xs) 0;
|
|
}
|
|
|
|
.far-right-item i {
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.far-right-item:hover, .far-right-item.active {
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
/* --- Common Button Styles --- */
|
|
.btn {
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
transition: background-color 0.2s ease, border-color 0.2s ease;
|
|
font-size: 0.9rem;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--spacing-xs);
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
.btn-primary {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
border-color: var(--primary-color);
|
|
}
|
|
.btn-primary:hover {
|
|
background-color: #e00038;
|
|
border-color: #e00038;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background-color: var(--secondary-color);
|
|
color: var(--text-light);
|
|
border-color: var(--secondary-color);
|
|
}
|
|
.btn-secondary:hover {
|
|
background-color: #4a546d;
|
|
border-color: #4a546d;
|
|
}
|
|
|
|
.btn-danger {
|
|
background-color: #dc3545;
|
|
color: white;
|
|
border-color: #dc3545;
|
|
}
|
|
.btn-danger:hover {
|
|
background-color: #c82333;
|
|
border-color: #bd2130;
|
|
}
|
|
|
|
.btn-icon {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-light);
|
|
font-size: 1.1rem;
|
|
cursor: pointer;
|
|
padding: var(--spacing-xs);
|
|
border-radius: var(--radius-sm);
|
|
transition: background-color 0.2s ease, color 0.2s ease;
|
|
}
|
|
.btn-icon:hover {
|
|
background-color: var(--secondary-color);
|
|
color: var(--primary-color);
|
|
}
|
|
.btn-icon.active {
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
|
|
/* --- Modals --- */
|
|
.modal-backdrop {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0, 0, 0, 0.7);
|
|
display: none; /* Hidden by default */
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.modal {
|
|
background-color: white;
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
|
|
display: none; /* Hidden by default */
|
|
flex-direction: column;
|
|
position: relative;
|
|
max-width: 90%;
|
|
max-height: 90%;
|
|
overflow: hidden;
|
|
color: #333; /* Texto oscuro para modal */
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--spacing-md) var(--spacing-lg);
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.modal-header h3 {
|
|
margin: 0;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.close-modal {
|
|
background: none;
|
|
border: none;
|
|
font-size: 1.2rem;
|
|
cursor: pointer;
|
|
color: #888;
|
|
}
|
|
.close-modal:hover {
|
|
color: #333;
|
|
}
|
|
|
|
.modal-content {
|
|
padding: var(--spacing-lg);
|
|
overflow-y: auto; /* Scroll dentro del contenido del modal */
|
|
}
|
|
|
|
/* Specific styles for Configuration Modal */
|
|
#config-modal {
|
|
width: 800px;
|
|
height: 550px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.modal-content-flex {
|
|
display: flex;
|
|
flex: 1;
|
|
}
|
|
|
|
.modal-nav {
|
|
width: 200px;
|
|
border-right: 1px solid #eee;
|
|
padding: var(--spacing-lg) 0;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.modal-nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
padding: var(--spacing-sm) var(--spacing-lg);
|
|
cursor: pointer;
|
|
color: #666;
|
|
transition: background-color 0.2s ease, color 0.2s ease;
|
|
}
|
|
|
|
.modal-nav-item:hover {
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
.modal-nav-item.active {
|
|
background-color: #e6f2ff; /* Azul claro para activo */
|
|
color: var(--primary-color);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.modal-settings-content {
|
|
flex: 1;
|
|
padding: var(--spacing-lg);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.config-tab-content {
|
|
display: none;
|
|
}
|
|
.config-tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
.setting-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: var(--spacing-md);
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.setting-row span {
|
|
flex-shrink: 0;
|
|
margin-right: var(--spacing-md);
|
|
}
|
|
|
|
.setting-row select, .setting-row input[type="text"] {
|
|
flex-grow: 1;
|
|
padding: var(--spacing-xs);
|
|
border: 1px solid #ccc;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.setting-row .btn {
|
|
margin-left: var(--spacing-sm);
|
|
}
|
|
|
|
.checkbox-container, .radio-container {
|
|
display: block;
|
|
position: relative;
|
|
padding-left: 25px;
|
|
margin-bottom: 12px;
|
|
cursor: pointer;
|
|
font-size: 0.95rem;
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
user-select: none;
|
|
}
|
|
|
|
.checkbox-container input, .radio-container input {
|
|
position: absolute;
|
|
opacity: 0;
|
|
cursor: pointer;
|
|
height: 0;
|
|
width: 0;
|
|
}
|
|
|
|
.checkmark, .radiomark {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
height: 18px;
|
|
width: 18px;
|
|
background-color: #eee;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.radio-container .radiomark {
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.checkbox-container:hover input ~ .checkmark,
|
|
.radio-container:hover input ~ .radiomark {
|
|
background-color: #ccc;
|
|
}
|
|
|
|
.checkbox-container input:checked ~ .checkmark,
|
|
.radio-container input:checked ~ .radiomark {
|
|
background-color: var(--primary-color);
|
|
}
|
|
|
|
.checkmark:after {
|
|
content: "";
|
|
position: absolute;
|
|
display: none;
|
|
}
|
|
.checkbox-container input:checked ~ .checkmark:after {
|
|
display: block;
|
|
}
|
|
.checkbox-container .checkmark:after {
|
|
left: 6px;
|
|
top: 3px;
|
|
width: 5px;
|
|
height: 10px;
|
|
border: solid white;
|
|
border-width: 0 2px 2px 0;
|
|
-webkit-transform: rotate(45deg);
|
|
-ms-transform: rotate(45deg);
|
|
transform: rotate(45deg);
|
|
}
|
|
|
|
.radiomark:after {
|
|
content: "";
|
|
position: absolute;
|
|
display: none;
|
|
}
|
|
.radio-container input:checked ~ .radiomark:after {
|
|
display: block;
|
|
}
|
|
.radio-container .radiomark:after {
|
|
top: 5px;
|
|
left: 5px;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: white;
|
|
}
|
|
|
|
.small-text {
|
|
font-size: 0.85rem;
|
|
color: #666;
|
|
margin-top: -10px;
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
.small-text a {
|
|
color: var(--primary-color);
|
|
text-decoration: none;
|
|
}
|
|
.small-text-muted {
|
|
font-size: 0.85rem;
|
|
color: #999;
|
|
}
|
|
.small-text-muted.indent {
|
|
margin-left: 25px; /* Para alinear con checkboxes */
|
|
}
|
|
|
|
.setting-row-img {
|
|
display: flex;
|
|
gap: var(--spacing-lg);
|
|
margin-bottom: var(--spacing-md);
|
|
align-items: flex-start;
|
|
}
|
|
.setting-row-img img {
|
|
width: 150px;
|
|
height: 84px;
|
|
object-fit: cover;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.camera-settings {
|
|
flex-grow: 1;
|
|
}
|
|
.camera-settings .setting-row {
|
|
margin-bottom: var(--spacing-sm);
|
|
}
|
|
|
|
.alert-box {
|
|
background-color: #fff3cd; /* Amarillo claro */
|
|
color: #856404; /* Texto amarillo oscuro */
|
|
border: 1px solid #ffeeba;
|
|
border-radius: var(--radius-md);
|
|
padding: var(--spacing-md);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
text-align: center;
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-md);
|
|
}
|
|
.alert-box i {
|
|
font-size: 1.5rem;
|
|
}
|
|
.alert-box button {
|
|
background-color: #ffc107; /* Amarillo oscuro para botón */
|
|
border-color: #ffc107;
|
|
color: #333;
|
|
}
|
|
.alert-box button:hover {
|
|
background-color: #e0a800;
|
|
}
|
|
|
|
.info-box {
|
|
background-color: #d1ecf1; /* Azul claro */
|
|
color: #0c5460; /* Texto azul oscuro */
|
|
border: 1px solid #bee5eb;
|
|
border-radius: var(--radius-md);
|
|
padding: var(--spacing-md);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-md);
|
|
}
|
|
.info-box i {
|
|
font-size: 1.2rem;
|
|
flex-shrink: 0;
|
|
}
|
|
.info-box p {
|
|
margin: 0;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.shortcuts-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-md);
|
|
}
|
|
.shortcut-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
.shortcut-item span {
|
|
flex-grow: 1;
|
|
}
|
|
.shortcut-item input {
|
|
width: 120px;
|
|
text-align: center;
|
|
background-color: #f0f0f0;
|
|
border: 1px solid #ddd;
|
|
padding: 5px 10px;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.shortcut-item i {
|
|
color: #999;
|
|
cursor: pointer;
|
|
}
|
|
.shortcut-item i:hover {
|
|
color: #dc3545;
|
|
}
|
|
|
|
.design-checkbox-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: var(--spacing-sm);
|
|
margin-top: var(--spacing-md);
|
|
}
|
|
.design-checkbox {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
padding-left: 30px; /* Ajustar padding para ícono */
|
|
}
|
|
.design-checkbox .checkmark {
|
|
top: 5px;
|
|
}
|
|
.design-checkbox i {
|
|
font-size: 1.1rem;
|
|
margin-right: 5px;
|
|
color: #666;
|
|
}
|
|
|
|
#guests textarea {
|
|
width: 100%;
|
|
min-height: 80px;
|
|
background-color: #f0f0f0;
|
|
border: 1px solid #ddd;
|
|
border-radius: var(--radius-sm);
|
|
padding: var(--spacing-sm);
|
|
resize: vertical;
|
|
font-family: 'Inter', sans-serif;
|
|
font-size: 0.9rem;
|
|
color: #333;
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
|
|
/* Specific styles for Media Selection Modal */
|
|
#media-selection-modal {
|
|
width: 800px;
|
|
height: 600px;
|
|
}
|
|
.upload-area {
|
|
background-color: #f0f0f0;
|
|
border: 2px dashed #ddd;
|
|
border-radius: var(--radius-md);
|
|
padding: var(--spacing-lg);
|
|
text-align: center;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
.upload-area i {
|
|
font-size: 3rem;
|
|
color: #ccc;
|
|
}
|
|
.upload-area p {
|
|
margin: 0;
|
|
color: #666;
|
|
}
|
|
.media-tabs {
|
|
display: flex;
|
|
border-bottom: 1px solid #eee;
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
.media-tab-item {
|
|
background: none;
|
|
border: none;
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
cursor: pointer;
|
|
color: #666;
|
|
font-size: 1rem;
|
|
border-bottom: 2px solid transparent;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.media-tab-item:hover {
|
|
color: #333;
|
|
}
|
|
.media-tab-item.active {
|
|
color: var(--primary-color);
|
|
border-bottom-color: var(--primary-color);
|
|
font-weight: 500;
|
|
}
|
|
.media-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
gap: var(--spacing-sm);
|
|
overflow-y: auto;
|
|
flex: 1;
|
|
}
|
|
.media-item {
|
|
border: 1px solid #eee;
|
|
border-radius: var(--radius-md);
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
text-align: center;
|
|
padding-bottom: var(--spacing-xs);
|
|
}
|
|
.media-item:hover {
|
|
border-color: var(--primary-color);
|
|
box-shadow: 0 0 5px rgba(var(--primary-color), 0.5);
|
|
}
|
|
.media-item img {
|
|
width: 100%;
|
|
height: 70px; /* Altura fija para miniaturas */
|
|
object-fit: cover;
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
}
|
|
.media-item span {
|
|
font-size: 0.8rem;
|
|
color: #333;
|
|
}
|
|
|
|
|
|
/* --- Tooltip Styles --- */
|
|
[data-tooltip] {
|
|
position: relative;
|
|
cursor: help;
|
|
}
|
|
[data-tooltip]:hover::after {
|
|
content: attr(data-tooltip);
|
|
position: absolute;
|
|
background-color: var(--background-medium);
|
|
color: var(--text-light);
|
|
padding: 6px 10px;
|
|
border-radius: var(--radius-sm);
|
|
font-size: 0.75rem;
|
|
white-space: nowrap;
|
|
z-index: 1001;
|
|
bottom: calc(100% + 8px); /* Posición arriba del elemento */
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
pointer-events: none; /* Asegura que el tooltip no interfiera con el mouse */
|
|
opacity: 0;
|
|
animation: fade-in 0.2s forwards;
|
|
}
|
|
|
|
[data-tooltip]:hover::before {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: calc(100% + 4px); /* Triángulo debajo del tooltip */
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
border-width: 4px;
|
|
border-style: solid;
|
|
border-color: var(--background-medium) transparent transparent transparent;
|
|
z-index: 1001;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
animation: fade-in 0.2s forwards;
|
|
}
|
|
|
|
/* Animación para el tooltip */
|
|
@keyframes fade-in {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
/* Scrollbar personalizado */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
::-webkit-scrollbar-track {
|
|
background: var(--background-dark);
|
|
border-radius: 4px;
|
|
}
|
|
::-webkit-scrollbar-thumb {
|
|
background: var(--secondary-color);
|
|
border-radius: 4px;
|
|
}
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: var(--border-color);
|
|
}
|
|
```
|
|
|
|
### **3. JavaScript (`script.js`)**
|
|
|
|
Este script implementa la lógica de UX para el feedback instantáneo, la gestión de modales, pestañas, y la simulación de algunas funcionalidades.
|
|
|
|
```javascript
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
// --- Global State Management (Conceptual) ---
|
|
const appState = {
|
|
currentSceneId: '1',
|
|
activeBrand: 'brand1',
|
|
brandColor: '#ff0040',
|
|
activeTheme: 'block',
|
|
showNames: true,
|
|
showTitles: false,
|
|
logoSrc: 'https://via.placeholder.com/50/aaaaaa/ffffff?text=Logo',
|
|
logoPosition: 'top-left',
|
|
activeOverlay: 'none',
|
|
activeBackground: 'none',
|
|
activeBackgroundImg: '', // To store actual image src
|
|
soundVolume: 70,
|
|
musicVolume: 50,
|
|
micOn: true,
|
|
camOn: true,
|
|
participantsOnStage: {
|
|
'cesar': {
|
|
name: 'Cesar Mendivil',
|
|
onStage: true,
|
|
mic: true,
|
|
cam: true,
|
|
image: 'https://via.placeholder.com/60/777777/ffffff?text=CM'
|
|
},
|
|
// Add other participants here as needed
|
|
},
|
|
// State for active right sidebar panel
|
|
activeRightPanel: 'activos-multimedia', // Default to multimedia for demo
|
|
activeConfigModalTab: 'general',
|
|
qrCodeFormVisible: false,
|
|
};
|
|
|
|
// --- DOM Elements Cache ---
|
|
const elements = {
|
|
// Main layout
|
|
scenesList: document.getElementById('scenes-list'),
|
|
liveVideoStream: document.getElementById('live-video-stream'),
|
|
participantFeed: document.getElementById('participant-feed'),
|
|
mainParticipantTag: document.getElementById('main-participant-tag'),
|
|
// Right sidebar
|
|
rightSidebarTabs: document.querySelectorAll('.sidebar-tabs .tab-item'),
|
|
rightSidebarContents: document.querySelectorAll('.sidebar-right .tab-content'),
|
|
brandColorInput: document.querySelector('[data-setting="brand-color"]'),
|
|
brandColorValue: document.getElementById('brand-color-value'),
|
|
themeButtons: document.querySelectorAll('[data-setting="theme"] .btn-theme'),
|
|
showNamesToggle: document.querySelector('[data-setting="show-names"]'),
|
|
showTitlesToggle: document.querySelector('[data-setting="show-titles"]'),
|
|
logoPreview: document.getElementById('logo-preview').querySelector('img'),
|
|
logoFileInput: document.getElementById('logo-file-input'),
|
|
uploadLogoBtn: document.querySelector('[data-action="upload-logo"]'),
|
|
logoPositionButtons: document.querySelectorAll('[data-setting="logo-position"] .btn-icon'),
|
|
overlayItems: document.querySelectorAll('[data-setting="overlay"] .overlay-item'),
|
|
backgroundItems: document.querySelectorAll('[data-setting="background-video"] .background-item'),
|
|
stageOverlay: document.getElementById('stage-overlay'),
|
|
stageLogo: document.getElementById('stage-logo'),
|
|
stageBackground: document.getElementById('stage-background'),
|
|
qrCodeGenerator: document.getElementById('qr-code-generator'),
|
|
addQrCodeBtn: document.querySelector('[data-action="add-qr-code"]'),
|
|
qrCodeForm: document.querySelector('.qr-code-form'),
|
|
createQrBtn: document.querySelector('[data-action="create-qr"]'),
|
|
cancelQrBtn: document.querySelector('[data-action="cancel-qr"]'),
|
|
qrTitleInput: document.querySelector('[data-qr-title]'),
|
|
qrUrlInput: document.querySelector('[data-qr-url]'),
|
|
soundVolumeSlider: document.querySelector('[data-setting="sound-volume"]'),
|
|
musicVolumeSlider: document.querySelector('[data-setting="music-volume"]'),
|
|
// Modals
|
|
modalBackdrop: document.getElementById('modal-backdrop'),
|
|
configModal: document.getElementById('config-modal'),
|
|
configModalCloseBtn: document.querySelector('#config-modal [data-action="close-modal"]'),
|
|
configModalNavItems: document.querySelectorAll('.modal-nav-item'),
|
|
configModalContents: document.querySelectorAll('.config-tab-content'),
|
|
mediaSelectionModal: document.getElementById('media-selection-modal'),
|
|
mediaModalTitle: document.getElementById('media-modal-title'),
|
|
mediaGrid: document.getElementById('media-grid'),
|
|
mediaModalCloseBtn: document.querySelector('#media-selection-modal [data-action="close-modal"]'),
|
|
mediaTabItems: document.querySelectorAll('.media-tab-item'),
|
|
// Far right panel
|
|
farRightPanelItems: document.querySelectorAll('.sidebar-far-right .far-right-item'),
|
|
// Controls
|
|
micToggles: document.querySelectorAll('[data-mic-toggle]'),
|
|
camToggles: document.querySelectorAll('[data-cam-toggle]'),
|
|
participantCards: document.querySelectorAll('.participants-grid .participant-card'),
|
|
// Scene options dropdown
|
|
sceneOptionsToggles: document.querySelectorAll('.scene-options i.fa-ellipsis-v'),
|
|
|
|
};
|
|
|
|
// --- Core UI Update Functions ---
|
|
|
|
/**
|
|
* Renders the current active scene in the main video player area.
|
|
* Updates background, overlay, logo, and participant visibility based on appState.
|
|
*/
|
|
function renderLiveView() {
|
|
// Update Brand Color (for elements like name tags)
|
|
document.documentElement.style.setProperty('--primary-color', appState.brandColor);
|
|
|
|
// Update Logo
|
|
if (appState.logoSrc && appState.activeLogo !== 'none') { // 'none' could be a conceptual value to hide
|
|
elements.stageLogo.src = appState.logoSrc;
|
|
elements.stageLogo.style.display = 'block';
|
|
// Simple positioning based on state
|
|
if (appState.logoPosition === 'top-left') {
|
|
elements.stageLogo.style.top = 'var(--spacing-sm)';
|
|
elements.stageLogo.style.left = 'var(--spacing-sm)';
|
|
elements.stageLogo.style.right = 'auto';
|
|
} else if (appState.logoPosition === 'top-right') {
|
|
elements.stageLogo.style.top = 'var(--spacing-sm)';
|
|
elements.stageLogo.style.right = 'var(--spacing-sm)';
|
|
elements.stageLogo.style.left = 'auto';
|
|
}
|
|
} else {
|
|
elements.stageLogo.style.display = 'none';
|
|
}
|
|
|
|
// Update Overlay
|
|
if (appState.activeOverlay === 'streamyard-live') {
|
|
elements.stageOverlay.src = 'https://via.placeholder.com/100x56/ff0040/ffffff?text=EN+VIVO';
|
|
elements.stageOverlay.style.display = 'block';
|
|
} else {
|
|
elements.stageOverlay.style.display = 'none';
|
|
}
|
|
|
|
// Update Background
|
|
if (appState.activeBackground !== 'none' && appState.activeBackgroundImg) {
|
|
elements.stageBackground.style.backgroundImage = `url(${appState.activeBackgroundImg})`;
|
|
elements.stageBackground.style.display = 'block';
|
|
} else {
|
|
elements.stageBackground.style.backgroundImage = 'none';
|
|
elements.stageBackground.style.display = 'none';
|
|
}
|
|
|
|
// Update participant visibility and name tag
|
|
const mainParticipant = appState.participantsOnStage['cesar']; // Assuming 'cesar' is the main one
|
|
if (mainParticipant && mainParticipant.onStage) {
|
|
elements.liveVideoStream.src = mainParticipant.image.replace('60', '800x450'); // Simulate full-size video
|
|
if (appState.showNames) {
|
|
elements.mainParticipantTag.textContent = mainParticipant.name;
|
|
elements.mainParticipantTag.style.display = 'block';
|
|
} else {
|
|
elements.mainParticipantTag.style.display = 'none';
|
|
}
|
|
} else {
|
|
elements.liveVideoStream.src = 'https://via.placeholder.com/800x450/1e2430/ffffff?text=Vista+Previa+de+la+Transmision'; // Default placeholder
|
|
elements.mainParticipantTag.style.display = 'none';
|
|
}
|
|
|
|
// Update mic/cam icons for active participant (if any)
|
|
elements.micToggles.forEach(btn => {
|
|
if (appState.micOn) {
|
|
btn.classList.add('active');
|
|
} else {
|
|
btn.classList.remove('active');
|
|
}
|
|
});
|
|
elements.camToggles.forEach(btn => {
|
|
if (appState.camOn) {
|
|
btn.classList.add('active');
|
|
} else {
|
|
btn.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
console.log("Live view rendered with state:", appState);
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates the UI elements in the right sidebar based on current appState.
|
|
*/
|
|
function updateRightSidebarUI() {
|
|
// Brand Color
|
|
elements.brandColorInput.value = appState.brandColor;
|
|
elements.brandColorValue.textContent = appState.brandColor;
|
|
|
|
// Theme Buttons
|
|
elements.themeButtons.forEach(btn => {
|
|
if (btn.dataset.theme === appState.activeTheme) {
|
|
btn.classList.add('active');
|
|
} else {
|
|
btn.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// Show Names Toggle
|
|
elements.showNamesToggle.checked = appState.showNames;
|
|
// Show Titles Toggle
|
|
elements.showTitlesToggle.checked = appState.showTitles;
|
|
|
|
// Logo
|
|
elements.logoPreview.src = appState.logoSrc;
|
|
elements.logoPositionButtons.forEach(btn => {
|
|
if (btn.dataset.position === appState.logoPosition) {
|
|
btn.classList.add('active');
|
|
} else {
|
|
btn.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// Overlay
|
|
elements.overlayItems.forEach(item => {
|
|
if (item.dataset.overlay === appState.activeOverlay) {
|
|
item.classList.add('active');
|
|
} else {
|
|
item.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// Background
|
|
elements.backgroundItems.forEach(item => {
|
|
if (item.dataset.background === appState.activeBackground) {
|
|
item.classList.add('active');
|
|
} else {
|
|
item.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// QR Code form visibility
|
|
if (appState.qrCodeFormVisible) {
|
|
elements.qrCodeForm.style.display = 'flex';
|
|
} else {
|
|
elements.qrCodeForm.style.display = 'none';
|
|
}
|
|
|
|
// Sound Volumes
|
|
elements.soundVolumeSlider.value = appState.soundVolume;
|
|
elements.musicVolumeSlider.value = appState.musicVolume;
|
|
}
|
|
|
|
|
|
// --- Event Listeners and Handlers ---
|
|
|
|
// Right Sidebar Tabs (Estilo, Multimedia)
|
|
elements.rightSidebarTabs.forEach(tab => {
|
|
tab.addEventListener('click', () => {
|
|
elements.rightSidebarTabs.forEach(item => item.classList.remove('active'));
|
|
tab.classList.add('active');
|
|
|
|
elements.rightSidebarContents.forEach(content => content.classList.remove('active'));
|
|
const targetContent = document.getElementById(tab.dataset.tab);
|
|
if (targetContent) {
|
|
targetContent.classList.add('active');
|
|
}
|
|
appState.activeRightPanel = tab.dataset.tab; // Update state
|
|
console.log(`Changed to right sidebar tab: ${appState.activeRightPanel}`);
|
|
});
|
|
});
|
|
|
|
// Brand Color Picker
|
|
elements.brandColorInput.addEventListener('input', (event) => {
|
|
appState.brandColor = event.target.value;
|
|
renderLiveView();
|
|
updateRightSidebarUI(); // Update value span
|
|
});
|
|
|
|
// Theme Buttons
|
|
elements.themeButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
appState.activeTheme = button.dataset.theme;
|
|
updateRightSidebarUI();
|
|
// In a real app, this would trigger visual theme changes
|
|
console.log(`Theme changed to: ${appState.activeTheme}`);
|
|
});
|
|
});
|
|
|
|
// Show Names Toggle
|
|
elements.showNamesToggle.addEventListener('change', (event) => {
|
|
appState.showNames = event.target.checked;
|
|
renderLiveView();
|
|
});
|
|
|
|
// Show Titles Toggle
|
|
elements.showTitlesToggle.addEventListener('change', (event) => {
|
|
appState.showTitles = event.target.checked;
|
|
renderLiveView();
|
|
});
|
|
|
|
// Logo Upload
|
|
elements.uploadLogoBtn.addEventListener('click', () => {
|
|
elements.logoFileInput.click();
|
|
});
|
|
elements.logoFileInput.addEventListener('change', (event) => {
|
|
const file = event.target.files[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
appState.logoSrc = e.target.result;
|
|
renderLiveView();
|
|
updateRightSidebarUI();
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
});
|
|
|
|
// Logo Position Buttons
|
|
elements.logoPositionButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
appState.logoPosition = button.dataset.position;
|
|
renderLiveView();
|
|
updateRightSidebarUI();
|
|
});
|
|
});
|
|
|
|
// Overlay Selection
|
|
elements.overlayItems.forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
appState.activeOverlay = item.dataset.overlay;
|
|
renderLiveView();
|
|
updateRightSidebarUI(); // To update active class
|
|
console.log(`Overlay changed to: ${appState.activeOverlay}`);
|
|
});
|
|
});
|
|
|
|
// Background Selection
|
|
elements.backgroundItems.forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
appState.activeBackground = item.dataset.background;
|
|
appState.activeBackgroundImg = item.dataset.img || ''; // Store image URL
|
|
renderLiveView();
|
|
updateRightSidebarUI(); // To update active class
|
|
console.log(`Background changed to: ${appState.activeBackground}`);
|
|
});
|
|
});
|
|
|
|
|
|
// QR Code Form Toggles
|
|
elements.addQrCodeBtn.addEventListener('click', () => {
|
|
appState.qrCodeFormVisible = true;
|
|
updateRightSidebarUI();
|
|
});
|
|
elements.cancelQrBtn.addEventListener('click', () => {
|
|
appState.qrCodeFormVisible = false;
|
|
elements.qrTitleInput.value = '';
|
|
elements.qrUrlInput.value = '';
|
|
updateRightSidebarUI();
|
|
});
|
|
elements.createQrBtn.addEventListener('click', () => {
|
|
const title = elements.qrTitleInput.value;
|
|
const url = elements.qrUrlInput.value;
|
|
if (title && url) {
|
|
console.log(`Created QR Code: Title - ${title}, URL - ${url}`);
|
|
// In a real app, generate QR code and add to UI
|
|
appState.qrCodeFormVisible = false;
|
|
elements.qrTitleInput.value = '';
|
|
elements.qrUrlInput.value = '';
|
|
updateRightSidebarUI();
|
|
} else {
|
|
alert('Por favor, ingresa un título y una URL para el código QR.');
|
|
}
|
|
});
|
|
|
|
|
|
// Sound Volume Slider
|
|
elements.soundVolumeSlider.addEventListener('input', (event) => {
|
|
appState.soundVolume = event.target.value;
|
|
console.log(`Sound volume: ${appState.soundVolume}`);
|
|
// In a real app, adjust actual audio volume
|
|
});
|
|
|
|
// Music Volume Slider
|
|
elements.musicVolumeSlider.addEventListener('input', (event) => {
|
|
appState.musicVolume = event.target.value;
|
|
console.log(`Music volume: ${appState.musicVolume}`);
|
|
// In a real app, adjust actual music volume
|
|
});
|
|
|
|
|
|
// --- Scenes List (Left Sidebar) ---
|
|
elements.scenesList.addEventListener('click', (event) => {
|
|
const sceneItem = event.target.closest('.scene-item');
|
|
if (sceneItem && !event.target.closest('.scene-options')) { // Avoid activating scene when clicking options
|
|
elements.scenesList.querySelectorAll('.scene-item').forEach(item => item.classList.remove('active'));
|
|
sceneItem.classList.add('active');
|
|
appState.currentSceneId = sceneItem.dataset.sceneId;
|
|
renderLiveView(); // Re-render live view for new scene
|
|
console.log(`Active scene changed to: ${appState.currentSceneId}`);
|
|
}
|
|
});
|
|
|
|
// Scene Options Dropdown Toggle
|
|
elements.scenesList.addEventListener('click', (event) => {
|
|
const ellipsisIcon = event.target.closest('.scene-options i.fa-ellipsis-v');
|
|
if (ellipsisIcon) {
|
|
const dropdown = ellipsisIcon.nextElementSibling;
|
|
dropdown.classList.toggle('active');
|
|
|
|
// Close other dropdowns
|
|
elements.scenesList.querySelectorAll('.dropdown-menu').forEach(menu => {
|
|
if (menu !== dropdown) {
|
|
menu.classList.remove('active');
|
|
}
|
|
});
|
|
} else if (!event.target.closest('.dropdown-menu')) {
|
|
// Close all dropdowns if click is outside
|
|
elements.scenesList.querySelectorAll('.dropdown-menu').forEach(menu => menu.classList.remove('active'));
|
|
}
|
|
|
|
const showOnStageBtn = event.target.closest('[data-action="show-on-stage"]');
|
|
if(showOnStageBtn) {
|
|
const sceneItem = showOnStageBtn.closest('.scene-item');
|
|
elements.scenesList.querySelectorAll('.scene-item').forEach(item => item.classList.remove('active'));
|
|
sceneItem.classList.add('active');
|
|
appState.currentSceneId = sceneItem.dataset.sceneId;
|
|
renderLiveView();
|
|
console.log(`Scene ${appState.currentSceneId} shown on stage.`);
|
|
showOnStageBtn.closest('.dropdown-menu').classList.remove('active'); // Close dropdown after action
|
|
}
|
|
});
|
|
|
|
// --- Participants Controls (Central Area) ---
|
|
elements.participantCards.forEach(card => {
|
|
card.addEventListener('click', () => {
|
|
if (card.dataset.participantId) {
|
|
const participantId = card.dataset.participantId;
|
|
if (appState.participantsOnStage[participantId]) {
|
|
const currentStatus = appState.participantsOnStage[participantId].onStage;
|
|
appState.participantsOnStage[participantId].onStage = !currentStatus; // Toggle on/off stage
|
|
card.classList.toggle('active', !currentStatus);
|
|
renderLiveView();
|
|
console.log(`Participant ${participantId} toggled stage status to: ${!currentStatus}`);
|
|
}
|
|
} else if (card.dataset.action === 'add-participant') {
|
|
console.log('Open invite participant modal.');
|
|
// Placeholder for opening an invite modal
|
|
}
|
|
});
|
|
});
|
|
|
|
elements.micToggles.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
appState.micOn = !appState.micOn;
|
|
renderLiveView();
|
|
console.log(`Microphone toggled: ${appState.micOn ? 'On' : 'Off'}`);
|
|
});
|
|
});
|
|
|
|
elements.camToggles.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
appState.camOn = !appState.camOn;
|
|
renderLiveView();
|
|
console.log(`Camera toggled: ${appState.camOn ? 'On' : 'Off'}`);
|
|
});
|
|
});
|
|
|
|
// --- Far Right Panel (Main Navigation) ---
|
|
elements.farRightPanelItems.forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
elements.farRightPanelItems.forEach(i => i.classList.remove('active'));
|
|
item.classList.add('active');
|
|
appState.activeRightPanel = item.dataset.panel;
|
|
|
|
// Simulate changing the right sidebar content
|
|
// Here you would dynamically load or show/hide different full panels in sidebar-right
|
|
// For this demo, we'll just log the change.
|
|
console.log(`Far right panel changed to: ${appState.activeRightPanel}`);
|
|
|
|
// To demonstrate, we can switch the right sidebar's main tab if it matches
|
|
const correspondingTab = document.querySelector(`.sidebar-tabs .tab-item[data-tab="${appState.activeRightPanel}"]`);
|
|
if (correspondingTab) {
|
|
correspondingTab.click(); // Programmatically click the tab
|
|
} else {
|
|
// If it's a different panel (like "Comentarios"), you'd load a different content structure
|
|
// For now, ensure only multimedia/estilo tabs are handled.
|
|
// Or you could make the right-sidebar itself dynamic, replacing its content entirely.
|
|
elements.rightSidebarTabs.forEach(tab => tab.classList.remove('active'));
|
|
elements.rightSidebarContents.forEach(content => content.classList.remove('active'));
|
|
// And then load specific content for, e.g., 'comentarios'
|
|
}
|
|
});
|
|
});
|
|
|
|
// --- Modals ---
|
|
function openModal(modalElement) {
|
|
elements.modalBackdrop.style.display = 'flex';
|
|
modalElement.style.display = 'flex';
|
|
// Add a class to body to prevent scrolling
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
function closeModal() {
|
|
elements.modalBackdrop.style.display = 'none';
|
|
elements.configModal.style.display = 'none';
|
|
elements.mediaSelectionModal.style.display = 'none';
|
|
document.body.style.overflow = ''; // Restore body scroll
|
|
}
|
|
|
|
// Open Config Modal
|
|
document.querySelector('[data-action="open-settings"]').addEventListener('click', () => {
|
|
openModal(elements.configModal);
|
|
// Ensure default tab is active on open
|
|
elements.configModalNavItems.forEach(item => item.classList.remove('active'));
|
|
elements.configModalContents.forEach(content => content.classList.remove('active'));
|
|
document.querySelector(`.modal-nav-item[data-config-tab="${appState.activeConfigModalTab}"]`).classList.add('active');
|
|
document.getElementById(appState.activeConfigModalTab).classList.add('active');
|
|
});
|
|
|
|
// Close Modals
|
|
elements.modalBackdrop.addEventListener('click', (event) => {
|
|
if (event.target === elements.modalBackdrop) { // Only close if clicking backdrop directly
|
|
closeModal();
|
|
}
|
|
});
|
|
elements.configModalCloseBtn.addEventListener('click', closeModal);
|
|
elements.mediaModalCloseBtn.addEventListener('click', closeModal);
|
|
|
|
// Config Modal Navigation
|
|
elements.configModalNavItems.forEach(navItem => {
|
|
navItem.addEventListener('click', () => {
|
|
elements.configModalNavItems.forEach(item => item.classList.remove('active'));
|
|
navItem.classList.add('active');
|
|
|
|
elements.configModalContents.forEach(content => content.classList.remove('active'));
|
|
const targetTabId = navItem.dataset.configTab;
|
|
document.getElementById(targetTabId).classList.add('active');
|
|
appState.activeConfigModalTab = targetTabId; // Update state
|
|
console.log(`Config modal tab changed to: ${targetTabId}`);
|
|
});
|
|
});
|
|
|
|
// Open Media Selection Modal (Background)
|
|
document.querySelector('[data-action="open-background-modal"]').addEventListener('click', () => {
|
|
elements.mediaModalTitle.textContent = 'Fondo';
|
|
openModal(elements.mediaSelectionModal);
|
|
// Load background media specific items
|
|
elements.mediaGrid.innerHTML = ''; // Clear previous items
|
|
// Filter media-items by type 'image' or 'video' if applicable
|
|
document.querySelectorAll('.media-item[data-media-type="image"]').forEach(item => {
|
|
const clone = item.cloneNode(true);
|
|
clone.addEventListener('click', (event) => {
|
|
const src = event.currentTarget.dataset.mediaSrc;
|
|
const backgroundName = event.currentTarget.querySelector('span').textContent;
|
|
appState.activeBackground = backgroundName;
|
|
appState.activeBackgroundImg = src;
|
|
renderLiveView();
|
|
updateRightSidebarUI(); // To update the smaller grid in sidebar
|
|
closeModal();
|
|
console.log(`Selected background: ${backgroundName}`);
|
|
});
|
|
elements.mediaGrid.appendChild(clone);
|
|
});
|
|
// Set images tab as active for backgrounds
|
|
elements.mediaTabItems.forEach(item => item.classList.remove('active'));
|
|
document.querySelector('.media-tab-item[data-media-type="images"]').classList.add('active');
|
|
});
|
|
|
|
// Open Media Selection Modal (Overlay)
|
|
document.querySelector('[data-action="open-overlay-modal"]').addEventListener('click', () => {
|
|
elements.mediaModalTitle.textContent = 'Superposición';
|
|
openModal(elements.mediaSelectionModal);
|
|
// Load overlay media specific items (similar to background but might be different assets)
|
|
elements.mediaGrid.innerHTML = ''; // Clear previous items
|
|
document.querySelectorAll('.media-item[data-media-type="image"]').forEach(item => { // Re-using image assets for demo
|
|
const clone = item.cloneNode(true);
|
|
clone.addEventListener('click', (event) => {
|
|
const src = event.currentTarget.dataset.mediaSrc;
|
|
const overlayName = event.currentTarget.querySelector('span').textContent;
|
|
appState.activeOverlay = overlayName; // Update with selected overlay
|
|
// Potentially store a specific overlay image if needed
|
|
renderLiveView();
|
|
updateRightSidebarUI();
|
|
closeModal();
|
|
console.log(`Selected overlay: ${overlayName}`);
|
|
});
|
|
elements.mediaGrid.appendChild(clone);
|
|
});
|
|
elements.mediaTabItems.forEach(item => item.classList.remove('active'));
|
|
document.querySelector('.media-tab-item[data-media-type="images"]').classList.add('active');
|
|
});
|
|
|
|
// Media Modal Tabs (Images/Videos)
|
|
elements.mediaTabItems.forEach(tab => {
|
|
tab.addEventListener('click', () => {
|
|
elements.mediaTabItems.forEach(item => item.classList.remove('active'));
|
|
tab.classList.add('active');
|
|
const mediaType = tab.dataset.mediaType;
|
|
|
|
// Filter/re-render media grid based on type
|
|
elements.mediaGrid.innerHTML = '';
|
|
document.querySelectorAll(`.media-item[data-media-type="${mediaType}"]`).forEach(item => {
|
|
const clone = item.cloneNode(true);
|
|
// Re-attach click listener for new clones if necessary, or use delegation
|
|
elements.mediaGrid.appendChild(clone);
|
|
});
|
|
console.log(`Media modal tab changed to: ${mediaType}`);
|
|
});
|
|
});
|
|
|
|
|
|
// --- Drag-and-Drop for Scenes (Conceptual) ---
|
|
let draggedScene = null;
|
|
|
|
elements.scenesList.addEventListener('dragstart', (event) => {
|
|
draggedScene = event.target.closest('.scene-item');
|
|
if (draggedScene) {
|
|
event.dataTransfer.effectAllowed = 'move';
|
|
// Optional: Store some data about the dragged item
|
|
event.dataTransfer.setData('text/plain', draggedScene.dataset.sceneId);
|
|
setTimeout(() => {
|
|
draggedScene.classList.add('dragging');
|
|
}, 0);
|
|
}
|
|
});
|
|
|
|
elements.scenesList.addEventListener('dragover', (event) => {
|
|
event.preventDefault(); // Allow drop
|
|
const targetItem = event.target.closest('.scene-item');
|
|
if (targetItem && targetItem !== draggedScene) {
|
|
const rect = targetItem.getBoundingClientRect();
|
|
const midpoint = rect.y + rect.height / 2;
|
|
if (event.clientY < midpoint) {
|
|
elements.scenesList.insertBefore(draggedScene, targetItem);
|
|
} else {
|
|
elements.scenesList.insertBefore(draggedScene, targetItem.nextSibling);
|
|
}
|
|
}
|
|
});
|
|
|
|
elements.scenesList.addEventListener('dragend', () => {
|
|
if (draggedScene) {
|
|
draggedScene.classList.remove('dragging');
|
|
draggedScene = null;
|
|
// In a real app, update the order in appState or backend here.
|
|
console.log("Scenes reordered. Update backend/state.");
|
|
}
|
|
});
|
|
|
|
|
|
// --- Initialization ---
|
|
function initializeUI() {
|
|
// Set initial active states for right sidebar and far right panel
|
|
elements.rightSidebarTabs[1].click(); // Activate "Activos multimedia" tab initially
|
|
elements.farRightPanelItems[2].click(); // Activate "Activos multimedia" in far right
|
|
|
|
renderLiveView(); // Render the initial state of the live view
|
|
updateRightSidebarUI(); // Update sidebar UI elements
|
|
console.log("UI initialized.");
|
|
}
|
|
|
|
initializeUI();
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### **Explicación de la Propuesta UX en el Código:**
|
|
|
|
1. **HTML5 (`index.html`):**
|
|
* **Estructura Semántica:** Utiliza `<header>`, `<main>`, `<aside>`, `<section>` para una estructura clara.
|
|
* **Controles Orientados a la UX:**
|
|
* `data-action` atributos en botones para indicar su propósito y facilitar el manejo de eventos en JS.
|
|
* `data-setting` atributos para elementos de configuración que afectan el estado.
|
|
* `data-scene-id`, `data-participant-id` para identificar elementos específicos.
|
|
* `beta-tag` y `new-tag` para destacar características nuevas/en beta.
|
|
* `premium-badge` para indicar funciones de pago.
|
|
* **Modales:** Estructura HTML para los modales de `Configuración` y `Selección de Medios`, con `modal-backdrop` para el overlay.
|
|
* **Elementos Dinámicos:** Placeholders (`<img id="live-video-stream">`, `<div id="participant-feed">`) donde JavaScript inyectará o modificará el contenido para reflejar los cambios en tiempo real.
|
|
* **Tooltips:** Atributos `data-tooltip` en varios elementos para proporcionar información adicional al pasar el ratón, mejorando la discoverability y learnability.
|
|
|
|
2. **CSS3 (`style.css`):**
|
|
* **Variables CSS:** Reafirma el uso de `--primary-color`, `--background-dark`, etc., para la coherencia visual y facilidad de mantenimiento, directamente aplicando los colores y espaciados identificados en el análisis pixel-perfect.
|
|
* **Layout Adaptativo:** Flexbox para la mayoría de los contenedores (top-header, main-content, sidebars, participant-controls), y Grid para componentes específicos (presets-grid, overlay-grid, etc.) para organizar los elementos de manera eficiente.
|
|
* **Estados Visuales Claros:** Clases `.active` para resaltar elementos seleccionados (escenas, pestañas, temas, presets).
|
|
* **Diseño de Componentes:** Estilos detallados para botones, toggles, dropdowns, input de color, sliders de volumen, checkbox/radio personalizados para mantener una interfaz limpia y moderna.
|
|
* **Tooltips Estilizados:** CSS para el `data-tooltip` que asegura que los tooltips sean discretos, legibles y aparezcan de forma consistente.
|
|
* **Modales:** Estilos para el backdrop, el contenedor del modal y su contenido, incluyendo la navegación lateral del modal de configuración, replicando la apariencia del video.
|
|
* **Scrollbars Personalizados:** Un toque extra para mantener la estética oscura en los scrollbars.
|
|
|
|
3. **JavaScript (`script.js`):**
|
|
* **Gestión de Estado Centralizada (`appState`):** Un objeto global que mantiene el estado actual de la aplicación (escena activa, color de marca, logo, superposiciones, estado de micrófono/cámara, etc.). Esto es crucial para un feedback visual instantáneo, ya que la UI se actualiza en función de este estado.
|
|
* **Funciones de Renderizado y Actualización:**
|
|
* `renderLiveView()`: La función más importante para la UX. Se encarga de actualizar el área central de transmisión (cambio de fondo, superposiciones, logos, visibilidad de nombres de participantes) **inmediatamente** después de un cambio en el `appState`. Esto proporciona el feedback visual instantáneo.
|
|
* `updateRightSidebarUI()`: Sincroniza los controles del panel derecho (sliders, toggles, active-states de botones) con el `appState`.
|
|
* **Delegación de Eventos:** Se utilizan event listeners en contenedores padre (ej., `scenesList`, `participantsList`) para manejar clics en elementos hijos que pueden ser añadidos o removidos dinámicamente, optimizando el rendimiento.
|
|
* **Manejo de Modales:** Funciones `openModal()` y `closeModal()` para gestionar la visibilidad del backdrop y los modales, y para controlar el `overflow` del `body` para evitar el scroll accidental detrás del modal.
|
|
* **Navegación por Pestañas y Subpestañas:** Lógica para cambiar la clase `active` en las pestañas y mostrar/ocultar el contenido correspondiente en el sidebar derecho y dentro del modal de configuración.
|
|
* **Simulación de Drag-and-Drop:** Una implementación básica para reordenar las escenas, mostrando cómo se manejaría visualmente el arrastre y la inserción.
|
|
* **Interacciones Específicas:**
|
|
* Cambio de color de marca: El `input type="color"` actualiza `appState.brandColor` y `renderLiveView()` cambia el `--primary-color` en CSS.
|
|
* Toggles: `showNamesToggle` actualiza `appState.showNames`, lo que afecta la visibilidad del nombre del participante en la vista previa.
|
|
* Selección de logo/superposición/fondo: Actualizan `appState` y `renderLiveView()` cambia las imágenes en el área central.
|
|
* QR Code form: Lógica para mostrar/ocultar el formulario.
|
|
* Controles de micrófono/cámara: Actualizan `appState` y cambian el icono de `active` en los botones.
|
|
* **Inicialización (`initializeUI`):** Asegura que la UI se cargue en un estado predecible y activo, activando el primer tab y panel por defecto, y renderizando la vista en vivo inicial.
|
|
|
|
Esta propuesta de código es un punto de partida sólido para construir una interfaz con la misma atención a los detalles de UX que se observa en el video de StreamYard. La clave es la estrecha relación entre la gestión del estado en JavaScript y la actualización visual instantánea del DOM a través de CSS. |