From 49e981a4b583c89737a3182070565a0b91671024 Mon Sep 17 00:00:00 2001 From: Dasemu Date: Wed, 14 Jan 2026 04:04:12 +0100 Subject: [PATCH] feat: enhance UI with screen lock, timer improvements and better styles - Implement screen lock for iOS/Android (Wake Lock API + video workaround) - Fix curtain reveal animation to prevent visual confusion - Add audio alarm when timer ends for game and deliberation phases - Improve overall UI/UX with scroll enhancements and mobile optimizations --- index.html | 116 +-- script.js | 355 +++++++-- styles.css | 2015 +++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 2038 insertions(+), 448 deletions(-) diff --git a/index.html b/index.html index 94d1877..6875738 100644 --- a/index.html +++ b/index.html @@ -4,11 +4,18 @@ Juego del Impostor + + + + +
+
+ @@ -18,6 +25,10 @@ EN + + - + +

Creado por Darรญo Sevilla

- +
-

๐Ÿ“– Reglas del Juego

-
-
-

๐ŸŽฏ Objetivo

-

Los civiles deben identificar a los impostores antes de que termine el tiempo.

-
- -
-

๐ŸŽฒ Preparaciรณn

-

1. Cada jugador recibe una palabra secreta

-

2. Los civiles reciben la misma palabra

-

3. Los impostores reciben una palabra diferente pero relacionada

-
- -
-

๐Ÿ—ฃ๏ธ Partida

-

1. Por turnos, cada jugador da un sinรณnimo o descripciรณn de su palabra

-

2. Intenta ser especรญfico pero no revelar tu palabra exacta

-

3. Los impostores deben intentar pasar desapercibidos

-
- -
-

๐Ÿ—ณ๏ธ Votaciรณn

-

1. Tras el tiempo de juego y deliberaciรณn, vota en secreto

-

2. Los mรกs votados son eliminados

-

3. Si todos los impostores son eliminados, ganan los civiles

-

4. Si queda algรบn impostor, ellos ganan

-
+

Reglas del Juego

+
+

Objetivo

+

Los civiles deben identificar a los impostores antes de que termine el tiempo.

- +
+

Preparaciรณn

+

1. Cada jugador recibe una palabra secreta

+

2. Los civiles reciben la misma palabra

+

3. Los impostores reciben una palabra diferente

+
+ + +
+ + +
+

Reglas del Juego

+
+

Partida

+

1. Por turnos, da un sinรณnimo de tu palabra

+

2. Sรฉ especรญfico pero no reveles tu palabra

+

3. Los impostores deben pasar desapercibidos

+
+
+

Votaciรณn

+

1. Tras deliberar, vota en secreto

+

2. Los mรกs votados son eliminados

+

3. Civiles ganan si eliminan a todos los impostores

+
+ +
-

โš™๏ธ Configuraciรณn

+

Configuraciรณn

@@ -91,27 +104,32 @@
-
- -
-
-
+ + +
+ + +
+

Selecciรณn de Pools

+

Toca para seleccionar las categorรญas de palabras que quieres usar en la partida.

+
+
- +
-

๐Ÿ‘ฅ Nombres de jugadores

+

Nombres de jugadores

- +
-

๐ŸŽฒ Listo para revelar

+

Listo para revelar

Cada jugador debe ver su rol en secreto. Desliza la cortina hacia arriba para revelar.

@@ -120,11 +138,11 @@
-

๐Ÿ” Revelaciรณn

+

Revelaciรณn

Turno de: Jugador 1
Los demรกs, no mirรฉis. Mantรฉn levantada la cortina para ver tu rol.

-
โฌ†๏ธ
+
โ–ฒ
LEVANTA LA CORTINA
@@ -138,7 +156,7 @@
-

๐ŸŽฎ Partida en curso

+

Partida en curso

A decir sinรณnimos!

3:00
@@ -146,7 +164,7 @@
-

๐Ÿ—ฃ๏ธ Deliberaciรณn

+

Deliberaciรณn

รšltimos argumentos antes de votar.

1:00
@@ -154,7 +172,7 @@
-

๐Ÿ—ณ๏ธ Votaciรณn secreta

+

Votaciรณn secreta

Pasa el mรณvil a Jugador. Elige 1 sospechoso(s).

@@ -162,7 +180,7 @@
-

๐Ÿ† Resultados

+

Resultados

diff --git a/script.js b/script.js index cd7c36a..05c0b74 100644 --- a/script.js +++ b/script.js @@ -5,6 +5,7 @@ const POOLS_CACHE_KEY = 'impostorWordPoolsV1'; const POOLS_MANIFEST_URL = 'word-pools/manifest.json'; const THEME_STORAGE_KEY = 'impostorGameTheme'; const LANGUAGE_STORAGE_KEY = 'impostorGameLanguage'; +const SCREEN_LOCK_STORAGE_KEY = 'impostorGameScreenLock'; // ---------- Internationalization system ---------- const TRANSLATIONS = { @@ -74,7 +75,9 @@ const TRANSLATIONS = { impostorsMustBeLess: 'Impostores debe ser menor que jugadores', animalsNature: 'Animales y Naturaleza', everydayObjects: 'Objetos Cotidianos', - exitGame: 'Salir de la partida' + exitGame: 'Salir de la partida', + poolsSelection: 'Selecciรณn de Pools', + poolsSelectionText: 'Toca para seleccionar las categorรญas de palabras que quieres usar en la partida.' }, en: { gameTitle: 'The Impostor Game', @@ -142,7 +145,9 @@ const TRANSLATIONS = { impostorsMustBeLess: 'Impostors must be less than players', animalsNature: 'Animals and Nature', everydayObjects: 'Everyday Objects', - exitGame: 'Exit Game' + exitGame: 'Exit Game', + poolsSelection: 'Pool Selection', + poolsSelectionText: 'Tap to select the word categories you want to use in the game.' } }; @@ -206,42 +211,42 @@ async function updateUI() { function updateStaticTexts() { // Welcome screen const welcomeTitle = document.querySelector('.welcome-title'); - if (welcomeTitle) welcomeTitle.innerHTML = `๐ŸŽญ ${t('gameTitle')}`; + if (welcomeTitle) welcomeTitle.textContent = t('gameTitle'); const welcomeSubtitle = document.querySelector('.welcome-subtitle'); if (welcomeSubtitle) welcomeSubtitle.textContent = t('gameSubtitle'); const playBtn = document.querySelector('.btn-primary'); - if (playBtn) playBtn.innerHTML = `โ–ถ๏ธ ${t('play')}`; + if (playBtn) playBtn.textContent = t('play'); const rulesBtn = document.querySelector('.btn-secondary'); - if (rulesBtn) rulesBtn.innerHTML = `๐Ÿ“– ${t('rules')}`; + if (rulesBtn) rulesBtn.textContent = t('rules'); const credits = document.querySelector('.welcome-credits'); if (credits) credits.textContent = t('createdBy'); // Rules screen const rulesTitle = document.querySelector('#rules-screen h1'); - if (rulesTitle) rulesTitle.innerHTML = `๐Ÿ“– ${t('rulesTitle')}`; + if (rulesTitle) rulesTitle.textContent = t('rulesTitle'); const ruleSections = document.querySelectorAll('.rule-section'); if (ruleSections.length >= 4) { - ruleSections[0].querySelector('h3').innerHTML = `๐ŸŽฏ ${t('objective')}`; + ruleSections[0].querySelector('h3').textContent = t('objective'); ruleSections[0].querySelector('p').innerHTML = t('objectiveText'); - ruleSections[1].querySelector('h3').innerHTML = `๐ŸŽฒ ${t('preparation')}`; + ruleSections[1].querySelector('h3').textContent = t('preparation'); const prepSteps = t('preparationSteps'); ruleSections[1].querySelectorAll('p').forEach((p, i) => { if (prepSteps[i]) p.textContent = `${i + 1}. ${prepSteps[i]}`; }); - ruleSections[2].querySelector('h3').innerHTML = `๐Ÿ—ฃ๏ธ ${t('gameplay')}`; + ruleSections[2].querySelector('h3').textContent = t('gameplay'); const gameSteps = t('gameplaySteps'); ruleSections[2].querySelectorAll('p').forEach((p, i) => { if (gameSteps[i]) p.textContent = `${i + 1}. ${gameSteps[i]}`; }); - ruleSections[3].querySelector('h3').innerHTML = `๐Ÿ—ณ๏ธ ${t('voting')}`; + ruleSections[3].querySelector('h3').textContent = t('voting'); const voteSteps = t('votingSteps'); ruleSections[3].querySelectorAll('p').forEach((p, i) => { if (voteSteps[i]) p.textContent = `${i + 1}. ${voteSteps[i]}`; @@ -250,7 +255,7 @@ function updateStaticTexts() { // Setup screen const setupTitle = document.querySelector('#setup-screen h1'); - if (setupTitle) setupTitle.innerHTML = `โš™๏ธ ${t('configuration')}`; + if (setupTitle) setupTitle.textContent = t('configuration'); const labels = { 'num-players': t('players'), @@ -264,45 +269,49 @@ function updateStaticTexts() { if (label) label.textContent = text + ':'; }); - const poolsLabel = document.querySelector('#setup-screen .form-group:last-of-type label'); - if (poolsLabel) poolsLabel.textContent = t('pools') + ':'; + // Pools screen + const poolsTitle = document.querySelector('#pools-screen h1'); + if (poolsTitle) poolsTitle.textContent = t('poolsSelection'); + + const poolsText = document.querySelector('#pools-screen .info-text'); + if (poolsText) poolsText.textContent = t('poolsSelectionText'); // Names screen const namesTitle = document.querySelector('#names-screen h1'); - if (namesTitle) namesTitle.innerHTML = `๐Ÿ‘ฅ ${t('playerNames')}`; + if (namesTitle) namesTitle.textContent = t('playerNames'); // Pre-reveal screen const preRevealTitle = document.querySelector('#pre-reveal-screen h1'); - if (preRevealTitle) preRevealTitle.innerHTML = `๐ŸŽฒ ${t('readyToReveal')}`; + if (preRevealTitle) preRevealTitle.textContent = t('readyToReveal'); const preRevealText = document.querySelector('#pre-reveal-screen .info-text:not(#config-summary)'); if (preRevealText) preRevealText.textContent = t('eachPlayerSecret'); // Reveal screen const revealTitle = document.querySelector('#reveal-screen h1'); - if (revealTitle) revealTitle.innerHTML = `๐Ÿ” ${t('revelation')}`; + if (revealTitle) revealTitle.textContent = t('revelation'); // Game screen const gameTitle = document.querySelector('#game-screen h1'); - if (gameTitle) gameTitle.innerHTML = `๐ŸŽฎ ${t('gameInProgress')}`; + if (gameTitle) gameTitle.textContent = t('gameInProgress'); const gameText = document.querySelector('#game-screen .info-text'); if (gameText) gameText.textContent = t('giveSynonyms'); // Deliberation screen const delibTitle = document.querySelector('#deliberation-screen h1'); - if (delibTitle) delibTitle.innerHTML = `๐Ÿ—ฃ๏ธ ${t('deliberation')}`; + if (delibTitle) delibTitle.textContent = t('deliberation'); const delibText = document.querySelector('#deliberation-screen .info-text'); if (delibText) delibText.textContent = t('lastArguments'); // Voting screen const votingTitle = document.querySelector('#voting-screen h1'); - if (votingTitle) votingTitle.innerHTML = `๐Ÿ—ณ๏ธ ${t('secretVoting')}`; + if (votingTitle) votingTitle.textContent = t('secretVoting'); // Results screen const resultsTitle = document.querySelector('#results-screen h1'); - if (resultsTitle) resultsTitle.innerHTML = `๐Ÿ† ${t('results')}`; + if (resultsTitle) resultsTitle.textContent = t('results'); // Buttons const backButtons = document.querySelectorAll('button.ghost'); @@ -314,7 +323,8 @@ function updateStaticTexts() { // Update all other buttons based on their onclick or content document.querySelectorAll('button').forEach(btn => { - if (btn.getAttribute('onclick') === 'goToNames()') btn.textContent = t('next'); + if (btn.getAttribute('onclick') === 'goToPools()') btn.textContent = t('next'); + else if (btn.getAttribute('onclick') === 'goToNames()') btn.textContent = t('next'); else if (btn.getAttribute('onclick') === 'startGame()') btn.textContent = t('startGame'); else if (btn.getAttribute('onclick') === "showScreen('reveal-screen'); loadCurrentReveal();") btn.textContent = t('startReveal'); else if (btn.id === 'next-player-btn') btn.textContent = t('nextPlayer') + ' โ†’'; @@ -561,7 +571,7 @@ function renderPoolButtons() { } // ---------- Setup and player names ---------- -function goToNames() { +function goToPools() { let nPlayers = parseInt(document.getElementById('num-players').value) || MIN_PLAYERS; nPlayers = Math.min(Math.max(nPlayers, MIN_PLAYERS), MAX_PLAYERS); const maxImpostors = Math.max(1, Math.floor(nPlayers / 2)); @@ -572,7 +582,14 @@ function goToNames() { let dTime = parseInt(document.getElementById('deliberation-time').value) || defaultDeliberation(gTime); dTime = Math.min(Math.max(dTime, 30), Math.round(900 / 3)); if (nImpostors >= nPlayers) { alert(t('impostorsMustBeLess')); return; } - state.numPlayers = nPlayers; state.numImpostors = nImpostors; state.gameTime = gTime; state.deliberationTime = dTime; + state.numPlayers = nPlayers; + state.numImpostors = nImpostors; + state.gameTime = gTime; + state.deliberationTime = dTime; + showScreen('pools-screen'); +} + +function goToNames() { buildNameInputs(); showScreen('names-screen'); } @@ -662,6 +679,10 @@ function renderSummary() { // ---------- Role revelation ---------- function loadCurrentReveal() { state.phase = 'reveal'; saveState(); + + // Activar Wake Lock para mantener pantalla encendida durante el juego + requestWakeLock(); + if (!state.revealOrder || state.revealOrder.length !== state.numPlayers) { const step = state.turnDirection === 'horario' ? 1 : -1; state.revealOrder = Array.from({length: state.numPlayers}, (_, k) => (state.startPlayer + step * k + state.numPlayers) % state.numPlayers); @@ -716,13 +737,17 @@ function nextReveal() { state.currentReveal++; saveState(); loadCurrentReveal(); // Curtain system with GRAVITY - The curtain always tends to fall // Supports both touch (mobile) and mouse (desktop) +// On desktop: curtain stays up while mouse button is held, even if cursor leaves the area let curtainState = { isRevealed: false }; +let curtainDragState = { + startY: null, + isDragging: false, + currentTranslateY: 0 +}; -(() => { +function initCurtainHandlers() { const curtain = document.getElementById('curtain'); - const cover = document.getElementById('curtain-cover'); - let startY = null; - let isDragging = false; + if (!curtain) return; // Function to get Y position from event (touch or mouse) const getY = (e) => { @@ -731,9 +756,9 @@ let curtainState = { isRevealed: false }; // Start function (touch and mouse) const handleStart = (e) => { - const coverEl = document.getElementById('curtain-cover'); - startY = getY(e); - isDragging = true; + curtainDragState.startY = getY(e); + curtainDragState.isDragging = true; + curtainDragState.currentTranslateY = 0; if (e.type === 'mousedown') { e.preventDefault(); // Prevent text selection on desktop } @@ -741,21 +766,22 @@ let curtainState = { isRevealed: false }; // Move function (touch and mouse) const handleMove = (e) => { - if (startY === null || !isDragging) return; + if (curtainDragState.startY === null || !curtainDragState.isDragging) return; const currentY = getY(e); - const dy = currentY - startY; + const dy = currentY - curtainDragState.startY; const coverEl = document.getElementById('curtain-cover'); + if (!coverEl) return; // Calculate displacement: negative = up, positive = down - // Limit upward movement (not beyond curtain height) - // and don't allow going below initial position (0) - const translateY = Math.max(Math.min(dy, 0), -cover.offsetHeight); + // Allow going further up than the curtain height (user can keep dragging up) + // but don't allow going below initial position (0) + curtainDragState.currentTranslateY = Math.min(dy, 0); - coverEl.style.transform = `translateY(${translateY}px)`; + coverEl.style.transform = `translateY(${curtainDragState.currentTranslateY}px)`; coverEl.style.transition = 'none'; // If lifted enough, show content - if (translateY < -80 && !curtainState.isRevealed) { + if (curtainDragState.currentTranslateY < -80 && !curtainState.isRevealed) { curtainState.isRevealed = true; const idx = state.revealOrder[state.currentReveal]; const role = state.roles[idx]; @@ -766,15 +792,14 @@ let curtainState = { isRevealed: false }; document.getElementById('word-text').textContent = word; } - if (e.type === 'mousemove') { - e.preventDefault(); // Prevent selection on desktop - } + e.preventDefault(); // Prevent selection }; // End function (touch and mouse) const handleEnd = (e) => { - if (!isDragging || startY === null) return; + if (!curtainDragState.isDragging || curtainDragState.startY === null) return; const coverEl = document.getElementById('curtain-cover'); + if (!coverEl) return; // ALWAYS bring the curtain down when released (GRAVITY) coverEl.style.transition = 'transform 0.4s ease'; @@ -791,32 +816,126 @@ let curtainState = { isRevealed: false }; }, 400); } - startY = null; - isDragging = false; + curtainDragState.startY = null; + curtainDragState.isDragging = false; + curtainDragState.currentTranslateY = 0; }; // Touch events (mobile) - curtain.addEventListener('touchstart', handleStart, {passive:true}); - curtain.addEventListener('touchmove', handleMove, {passive:true}); - curtain.addEventListener('touchend', handleEnd, {passive:true}); - curtain.addEventListener('touchcancel', handleEnd, {passive:true}); + curtain.addEventListener('touchstart', handleStart, {passive: false}); + curtain.addEventListener('touchmove', handleMove, {passive: false}); + curtain.addEventListener('touchend', handleEnd, {passive: true}); + curtain.addEventListener('touchcancel', handleEnd, {passive: true}); - // Mouse events (desktop) + // Mouse events (desktop) - start on curtain only curtain.addEventListener('mousedown', handleStart); - curtain.addEventListener('mousemove', handleMove); - curtain.addEventListener('mouseup', handleEnd); - curtain.addEventListener('mouseleave', (e) => { - // If mouse leaves area while dragging, release (gravity) - if (isDragging) { - handleEnd(e); + + // Mouse move and up events on WINDOW so we can track even when cursor leaves everything + window.addEventListener('mousemove', handleMove); + window.addEventListener('mouseup', handleEnd); +} + +// Initialize curtain handlers when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initCurtainHandlers); +} else { + initCurtainHandlers(); +} + +// ---------- Screen Wake Lock (prevent screen from sleeping during timers) ---------- +let wakeLock = null; +let wakeLockVideo = null; // For iOS workaround + +// Detect if device is iOS +function isIOS() { + return /iPad|iPhone|iPod/.test(navigator.userAgent) || + (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); +} + +// Check if screen lock is enabled in settings +function isScreenLockEnabled() { + const saved = localStorage.getItem(SCREEN_LOCK_STORAGE_KEY); + return saved === null ? true : saved === 'true'; // Default enabled +} + +// Save screen lock preference +function setScreenLockEnabled(enabled) { + localStorage.setItem(SCREEN_LOCK_STORAGE_KEY, enabled.toString()); + updateScreenLockButton(); +} + +async function requestWakeLock() { + if (!isScreenLockEnabled()) return; + + // Try native Wake Lock API first (works on Android Chrome, etc.) + if ('wakeLock' in navigator) { + try { + wakeLock = await navigator.wakeLock.request('screen'); + wakeLock.addEventListener('release', () => { + wakeLock = null; + }); + console.log('Wake Lock activated (native API)'); + return; + } catch (err) { + console.log('Wake lock request failed:', err); } - }); -})(); + } + + // Fallback for iOS - use hidden video loop + if (isIOS() && !wakeLockVideo) { + try { + wakeLockVideo = document.createElement('video'); + wakeLockVideo.setAttribute('playsinline', ''); + wakeLockVideo.setAttribute('muted', ''); + wakeLockVideo.style.position = 'fixed'; + wakeLockVideo.style.opacity = '0'; + wakeLockVideo.style.pointerEvents = 'none'; + wakeLockVideo.style.width = '1px'; + wakeLockVideo.style.height = '1px'; + + // Minimal base64 encoded video (1 frame, silent) + wakeLockVideo.src = 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAu1tZGF0AAACrQYF//+p3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE1NSByMjkwMSA3ZDBmZjIyIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxOCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTMgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAwWWIhAAz//727L4FNf2f0JcRLMXaSnA+KqSAgHc0wAAAAwAAAwAAJuKiZ0WFMeJsgAAAHGAFBCwCPCVC'; + wakeLockVideo.loop = true; + + document.body.appendChild(wakeLockVideo); + await wakeLockVideo.play(); + console.log('Wake Lock activated (iOS video workaround)'); + } catch (err) { + console.log('iOS wake lock workaround failed:', err); + } + } +} + +function releaseWakeLock() { + // Release native Wake Lock + if (wakeLock) { + wakeLock.release(); + wakeLock = null; + } + + // Stop iOS video workaround + if (wakeLockVideo) { + wakeLockVideo.pause(); + wakeLockVideo.remove(); + wakeLockVideo = null; + } +} + +// Re-request wake lock when page becomes visible again +document.addEventListener('visibilitychange', async () => { + if (document.visibilityState === 'visible' && (wakeLock !== null || wakeLockVideo !== null)) { + await requestWakeLock(); + } +}); // ---------- Timers ---------- let timerInterval = null; -function startPhaseTimer(phase, seconds, elementId, onEnd) { +async function startPhaseTimer(phase, seconds, elementId, onEnd) { if (timerInterval) clearInterval(timerInterval); + + // Request wake lock to keep screen on during timer + await requestWakeLock(); + const now = Date.now(); state.timerPhase = phase; state.timerEndAt = now + seconds*1000; @@ -825,7 +944,12 @@ function startPhaseTimer(phase, seconds, elementId, onEnd) { const tick = () => { const remaining = Math.max(0, Math.round((state.timerEndAt - Date.now())/1000)); updateTimerDisplay(el, remaining); - if (remaining <= 0) { clearInterval(timerInterval); playBeep(); onEnd(); } + if (remaining <= 0) { + clearInterval(timerInterval); + releaseWakeLock(); // Release wake lock when timer ends + playBeep(); + onEnd(); + } }; tick(); timerInterval = setInterval(tick, 1000); @@ -847,17 +971,48 @@ function updateTimerDisplay(el, remaining) { } function playBeep() { + // Play alarm sound - 3 ascending beeps pattern repeated twice const ctx = new (window.AudioContext || window.webkitAudioContext)(); - const osc = ctx.createOscillator(); const gain = ctx.createGain(); - osc.connect(gain); gain.connect(ctx.destination); osc.frequency.value = 820; osc.type = 'sine'; - gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.45); - osc.start(); osc.stop(ctx.currentTime + 0.45); + const now = ctx.currentTime; + + // Frequencies for alarm pattern (ascending) + const frequencies = [523, 659, 784]; // C5, E5, G5 + const beepDuration = 0.15; + const gapDuration = 0.08; + const patternGap = 0.3; + + let time = now; + + // Play pattern twice + for (let pattern = 0; pattern < 2; pattern++) { + for (let i = 0; i < frequencies.length; i++) { + const osc = ctx.createOscillator(); + const gain = ctx.createGain(); + + osc.connect(gain); + gain.connect(ctx.destination); + osc.frequency.value = frequencies[i]; + osc.type = 'square'; // More alarm-like sound + + gain.gain.setValueAtTime(0, time); + gain.gain.linearRampToValueAtTime(0.25, time + 0.02); + gain.gain.setValueAtTime(0.25, time + beepDuration - 0.02); + gain.gain.linearRampToValueAtTime(0, time + beepDuration); + + osc.start(time); + osc.stop(time + beepDuration); + + time += beepDuration + gapDuration; + } + time += patternGap; + } } // ---------- Game phases ---------- function startGamePhase() { state.phase = 'game'; saveState(); showScreen('game-screen'); startPhaseTimer('game', state.gameTime, 'game-timer', startDeliberationPhase); } function startDeliberationPhase() { state.phase = 'deliberation'; saveState(); showScreen('deliberation-screen'); startPhaseTimer('deliberation', state.deliberationTime, 'deliberation-timer', startVotingPhase); } function startVotingPhase(candidates = null, isTiebreak = false) { + releaseWakeLock(); // Release wake lock when voting starts (no timer) state.phase = 'voting'; state.votingPlayer = 0; state.votes = {}; @@ -868,8 +1023,8 @@ function startVotingPhase(candidates = null, isTiebreak = false) { renderVoting(); showScreen('voting-screen'); } -function skipToDeliberation() { if (timerInterval) clearInterval(timerInterval); startDeliberationPhase(); } -function skipToVoting() { if (timerInterval) clearInterval(timerInterval); startVotingPhase(); } +function skipToDeliberation() { if (timerInterval) clearInterval(timerInterval); releaseWakeLock(); startDeliberationPhase(); } +function skipToVoting() { if (timerInterval) clearInterval(timerInterval); releaseWakeLock(); startVotingPhase(); } function startTiebreakDeliberation(candidates) { state.phase = 'deliberation'; state.tiebreakCandidates = candidates; @@ -896,16 +1051,22 @@ function renderVoting() { pool.forEach(i => { const item = document.createElement('div'); item.className = 'player-item'; + + // Marcar como disabled ANTES de aรฑadir al DOM para que la animaciรณn correcta se aplique + if (i === state.votingPlayer) { + item.classList.add('disabled'); + // NO aplicar opacity inline - dejamos que CSS lo maneje con la animaciรณn + item.style.pointerEvents = 'none'; + } + item.textContent = state.playerNames[i]; if (state.votes[i]) item.innerHTML += `${t('votes')}: ${state.votes[i]}`; if (state.selections.includes(i)) item.classList.add('selected'); - if (i === state.votingPlayer) { - item.classList.add('disabled'); - item.style.opacity = '0.5'; - item.style.pointerEvents = 'none'; - } else { - item.onclick = () => toggleSelection(i, item); - } + + if (i !== state.votingPlayer) { + item.onclick = () => toggleSelection(i, item); + } + list.appendChild(item); }); updateConfirmButton(); @@ -973,6 +1134,10 @@ function handleVoteOutcome() { // ---------- Results ---------- function showResults(isTiebreak = false) { state.phase = 'results'; saveState(); + + // Liberar Wake Lock cuando termina la partida + releaseWakeLock(); + const executed = state.executed || []; let impostorsAlive = 0; state.roles.forEach((r,i) => { if (r === 'IMPOSTOR' && !executed.includes(i)) impostorsAlive++; }); @@ -1005,6 +1170,8 @@ function showScreen(id) { function newMatch() { clearState(); + releaseWakeLock(); // Make sure wake lock is released when exiting game + if (timerInterval) clearInterval(timerInterval); state = { ...state, phase:'welcome', timerEndAt:null, timerPhase:null, votingPool:null, isTiebreak:false, tiebreakCandidates:[] }; saveState(); showScreen('welcome-screen'); @@ -1023,14 +1190,20 @@ function confirmExitGame() { function updateExitButtonVisibility() { const exitBtn = document.getElementById('exit-game'); const langBtn = document.getElementById('language-toggle'); + const screenLockBtn = document.getElementById('screen-lock-toggle'); - // Show exit button and hide language toggle in all phases except welcome and setup + // Show exit button and hide language/screen-lock toggles in all phases except welcome and setup if (state.phase !== 'welcome' && state.phase !== 'setup') { exitBtn.classList.add('visible'); if (langBtn) langBtn.style.display = 'none'; + if (screenLockBtn) screenLockBtn.classList.remove('visible'); } else { exitBtn.classList.remove('visible'); if (langBtn) langBtn.style.display = 'inline-flex'; + // Only show screen lock button on iOS + if (screenLockBtn && isIOS()) { + screenLockBtn.classList.add('visible'); + } } } @@ -1067,6 +1240,35 @@ function toggleTheme() { const initialTheme = loadTheme(); applyTheme(initialTheme); +// ---------- Screen Lock Button ---------- +function updateScreenLockButton() { + const btn = document.getElementById('screen-lock-toggle'); + if (!btn) return; + + const enabled = isScreenLockEnabled(); + const icon = btn.querySelector('.screen-lock-icon'); + + if (enabled) { + btn.classList.add('active'); + btn.setAttribute('title', 'Bloqueo de pantalla activado'); + if (icon) icon.textContent = '๐Ÿ”’'; + } else { + btn.classList.remove('active'); + btn.setAttribute('title', 'Bloqueo de pantalla desactivado'); + if (icon) icon.textContent = '๐Ÿ”“'; + } +} + +function toggleScreenLock() { + const currentState = isScreenLockEnabled(); + setScreenLockEnabled(!currentState); + + // If disabling, release any active wake lock + if (currentState) { + releaseWakeLock(); + } +} + // Event listener for theme and language buttons document.addEventListener('DOMContentLoaded', () => { const themeToggle = document.getElementById('theme-toggle'); @@ -1079,6 +1281,12 @@ document.addEventListener('DOMContentLoaded', () => { languageToggle.addEventListener('click', toggleLanguage); } + const screenLockToggle = document.getElementById('screen-lock-toggle'); + if (screenLockToggle) { + screenLockToggle.addEventListener('click', toggleScreenLock); + updateScreenLockButton(); + } + // Initialize language currentLanguage = loadLanguage(); setLanguage(currentLanguage); @@ -1133,4 +1341,7 @@ document.addEventListener('DOMContentLoaded', () => { // Initialize exit button visibility updateExitButtonVisibility(); + + // Initialize screen lock button for iOS + initScreenLockButton(); })(); diff --git a/styles.css b/styles.css index 499ffa5..8e4ebaf 100644 --- a/styles.css +++ b/styles.css @@ -1,203 +1,526 @@ -/* Theme variables */ +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + EXPEDIENTE CLASIFICADO - IMPOSTOR GAME + Noir Cyberpunk Interrogation Aesthetic + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Special+Elite&display=swap'); + :root { - /* Light theme (default) */ - --bg-gradient-start: #667eea; - --bg-gradient-end: #764ba2; - --container-bg: rgba(255, 255, 255, 0.15); - --container-border: rgba(255, 255, 255, 0.2); - --text-primary: #ffffff; - --text-secondary: rgba(255, 255, 255, 0.85); - --text-tertiary: rgba(255, 255, 255, 0.7); - --input-bg: rgba(255, 255, 255, 0.95); - --input-text: #2d3748; - --input-focus: #f5576c; - --button-gradient-start: #f093fb; - --button-gradient-end: #f5576c; - --button-secondary-start: #a8edea; - --button-secondary-end: #fed6e3; - --button-ghost-bg: rgba(255, 255, 255, 0.15); - --card-bg: rgba(255, 255, 255, 0.18); - --card-hover: rgba(255, 255, 255, 0.25); - --border-color: rgba(255, 255, 255, 0.25); - --shadow-color: rgba(31, 38, 135, 0.37); - --info-bg: rgba(0, 0, 0, 0.2); - --curtain-bg: #2d3748; - --pool-gradient: rgba(102, 126, 234, 0.4); + /* LIGHT THEME: Interrogation Room */ + --bg-primary: #dcd9d2; + --bg-secondary: #c8c3b8; + --bg-overlay: rgba(0, 0, 0, 0.05); + + --surface-glass: rgba(255, 255, 255, 0.85); + --surface-card: rgba(255, 255, 255, 0.95); + --surface-hover: rgba(255, 255, 255, 1); + + --text-primary: #0a0a0a; + --text-secondary: #2a2a2a; + --text-tertiary: #5a5a5a; + --text-inverted: #ffffff; + + --accent-warning: #e6a73c; + --accent-danger: #d93626; + --accent-success: #2d8b3d; + --accent-info: #2e4e7a; + + --border-light: rgba(0, 0, 0, 0.18); + --border-medium: rgba(0, 0, 0, 0.35); + --border-heavy: rgba(0, 0, 0, 0.55); + + --shadow-sm: 0 3px 12px rgba(0, 0, 0, 0.15); + --shadow-md: 0 6px 24px rgba(0, 0, 0, 0.22); + --shadow-lg: 0 12px 48px rgba(0, 0, 0, 0.28); + --shadow-harsh: 6px 6px 0px rgba(0, 0, 0, 0.25); + + --grain-opacity: 0.05; + --scanline-opacity: 0.025; + + /* Spotlight effect */ + --spotlight-color: rgba(255, 235, 180, 0.08); + --vignette-intensity: 0.4; } -/* Dark theme */ [data-theme="dark"] { - --bg-gradient-start: #1a1a2e; - --bg-gradient-end: #16213e; - --container-bg: rgba(30, 30, 50, 0.85); - --container-border: rgba(255, 255, 255, 0.1); - --text-primary: #e0e0e0; - --text-secondary: rgba(224, 224, 224, 0.85); - --text-tertiary: rgba(224, 224, 224, 0.6); - --input-bg: rgba(40, 40, 60, 0.9); - --input-text: #e0e0e0; - --input-focus: #ff6b9d; - --button-gradient-start: #c850c0; - --button-gradient-end: #ff6b9d; - --button-secondary-start: #4158d0; - --button-secondary-end: #c850c0; - --button-ghost-bg: rgba(255, 255, 255, 0.08); - --card-bg: rgba(60, 60, 80, 0.5); - --card-hover: rgba(80, 80, 100, 0.6); - --border-color: rgba(255, 255, 255, 0.15); - --shadow-color: rgba(0, 0, 0, 0.5); - --info-bg: rgba(0, 0, 0, 0.4); - --curtain-bg: #1a1a2e; - --pool-gradient: rgba(30, 30, 50, 0.6); + /* DARK THEME: Night Investigation */ + --bg-primary: #050505; + --bg-secondary: #0f0f0f; + --bg-overlay: rgba(255, 255, 255, 0.03); + + --surface-glass: rgba(25, 25, 25, 0.9); + --surface-card: rgba(35, 35, 35, 0.95); + --surface-hover: rgba(45, 45, 45, 1); + + --text-primary: #f5f5f5; + --text-secondary: #d0d0d0; + --text-tertiary: #909090; + --text-inverted: #0a0a0a; + + --accent-warning: #ffb84d; + --accent-danger: #ff3d2e; + --accent-success: #3dd46b; + --accent-info: #4d8ce0; + + --border-light: rgba(255, 255, 255, 0.12); + --border-medium: rgba(255, 255, 255, 0.22); + --border-heavy: rgba(255, 255, 255, 0.35); + + --shadow-sm: 0 3px 12px rgba(0, 0, 0, 0.6); + --shadow-md: 0 6px 24px rgba(0, 0, 0, 0.8); + --shadow-lg: 0 12px 48px rgba(0, 0, 0, 0.95); + --shadow-harsh: 6px 6px 0px rgba(0, 0, 0, 0.7); + + --grain-opacity: 0.07; + --scanline-opacity: 0.035; + + /* Spotlight effect */ + --spotlight-color: rgba(255, 200, 100, 0.04); + --vignette-intensity: 0.6; +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + BASE STYLES & TYPOGRAPHY + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; } -/* Mobile styles and main UI */ -* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; } body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); + font-family: 'JetBrains Mono', 'Courier Prime', 'Courier New', monospace; + background: + radial-gradient(ellipse 80% 50% at 50% 20%, var(--spotlight-color) 0%, transparent 50%), + radial-gradient(circle at 20% 30%, rgba(230, 167, 60, 0.08) 0%, transparent 40%), + radial-gradient(circle at 80% 70%, rgba(217, 54, 38, 0.06) 0%, transparent 40%), + var(--bg-primary); min-height: 100vh; + min-height: 100dvh; display: flex; justify-content: center; - align-items: flex-start; - padding: 80px 10px 10px 10px; + align-items: center; + padding: 70px 16px 16px; color: var(--text-primary); - transition: background 0.3s ease; + position: relative; + overflow: hidden; + font-size: 14px; + letter-spacing: 0px; + transition: background 0.5s ease, color 0.3s ease; } + +/* Film grain texture overlay */ +body::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.5'/%3E%3C/svg%3E"); + opacity: var(--grain-opacity); + pointer-events: none; + z-index: 9999; + mix-blend-mode: overlay; + animation: grain 8s steps(10) infinite; +} + +@keyframes grain { + 0%, 100% { transform: translate(0, 0); } + 10% { transform: translate(-5%, -10%); } + 20% { transform: translate(-15%, 5%); } + 30% { transform: translate(7%, -25%); } + 40% { transform: translate(-5%, 25%); } + 50% { transform: translate(-15%, 10%); } + 60% { transform: translate(15%, 0%); } + 70% { transform: translate(0%, 15%); } + 80% { transform: translate(3%, 35%); } + 90% { transform: translate(-10%, 10%); } +} + +/* Scanlines */ +body::after { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + rgba(0, 0, 0, 0.1) 2px, + rgba(0, 0, 0, 0.1) 4px + ); + opacity: var(--scanline-opacity); + pointer-events: none; + z-index: 9998; +} + +/* Dramatic vignette overlay */ +.vignette-overlay { + position: fixed; + inset: 0; + background: radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,var(--vignette-intensity)) 100%); + pointer-events: none; + z-index: 9997; +} + +/* VHS interference effect */ +@keyframes vhsInterference { + 0%, 100% { opacity: 0; } + 5% { opacity: 0.03; transform: translateX(-2px); } + 10% { opacity: 0; } + 15% { opacity: 0.02; transform: translateX(1px); } + 20% { opacity: 0; } +} + +.vhs-line { + position: fixed; + left: 0; + width: 100%; + height: 3px; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.8), transparent); + pointer-events: none; + z-index: 9996; + animation: vhsScan 8s linear infinite; + opacity: 0.04; +} + +@keyframes vhsScan { + 0% { top: -10px; } + 100% { top: 110%; } +} + +h1 { + font-family: 'Bebas Neue', 'Crimson Text', Georgia, serif; + text-align: center; + margin-bottom: 20px; + font-size: 2.6em; + font-weight: 400; + letter-spacing: 4px; + text-transform: uppercase; + position: relative; + text-shadow: 3px 3px 0px var(--bg-secondary), 0 0 30px rgba(230, 167, 60, 0.2); + line-height: 1.1; + animation: titleReveal 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +@keyframes titleReveal { + from { + opacity: 0; + letter-spacing: 20px; + filter: blur(8px); + } + to { + opacity: 1; + letter-spacing: 4px; + filter: blur(0); + } +} + +h1::after { + content: ''; + display: block; + width: 80px; + height: 4px; + background: linear-gradient(90deg, var(--accent-danger) 0%, var(--accent-warning) 50%, var(--accent-danger) 100%); + background-size: 200% 100%; + margin: 14px auto 0; + box-shadow: 0 0 15px rgba(230, 167, 60, 0.5); + animation: shimmer 3s ease-in-out infinite; +} + +@keyframes shimmer { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } +} + +h2 { + font-family: 'Crimson Text', Georgia, serif; + text-align: center; + margin: 16px 0; + font-size: 1.4em; + font-weight: 700; + letter-spacing: 0.5px; +} + +h3 { + font-family: 'JetBrains Mono', monospace; + font-size: 1em; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 1.5px; + margin-bottom: 12px; +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + CONTAINER & SCREENS + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + .container { width: 100%; max-width: 480px; - background: var(--container-bg); - backdrop-filter: blur(10px); - border-radius: 20px; - padding: 20px; - box-shadow: 0 8px 32px var(--shadow-color); - border: 1px solid var(--container-border); + background: var(--surface-glass); + backdrop-filter: blur(20px) saturate(150%); + border-radius: 0; + padding: 28px 22px; + box-shadow: var(--shadow-harsh), var(--shadow-lg); + border: 4px solid var(--border-heavy); display: flex; flex-direction: column; - transition: background 0.3s ease, border 0.3s ease; + transition: all 0.4s ease; margin-bottom: 20px; + position: relative; + overflow: hidden; + clip-path: polygon( + 0 20px, + 20px 0, + 100% 0, + 100% calc(100% - 20px), + calc(100% - 20px) 100%, + 0 100% + ); } -h1 { text-align: center; margin-bottom: 12px; font-size: 1.5em; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); } -h2 { text-align: center; margin: 8px 0; font-size: 1.2em; } -.screen { display: none; animation: fadeIn 0.25s ease; flex: 1; overflow-y: auto; } -.screen.active { display: flex; flex-direction: column; } -@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } -.form-group { margin-bottom: 10px; } -.form-group.compact { margin-bottom: 6px; } -label { display: block; margin-bottom: 4px; font-weight: 700; font-size: 0.9em; color: var(--text-secondary); } + +.container::before { + content: 'โฌข CLASSIFIED โฌข'; + position: absolute; + top: 8px; + left: 50%; + transform: translateX(-50%); + font-size: 0.65em; + letter-spacing: 3px; + opacity: 0.4; + font-weight: 800; + color: var(--accent-danger); + text-shadow: 0 0 10px rgba(217, 54, 38, 0.3); + animation: classifiedPulse 4s ease-in-out infinite; +} + +@keyframes classifiedPulse { + 0%, 100% { opacity: 0.4; text-shadow: 0 0 10px rgba(217, 54, 38, 0.3); } + 50% { opacity: 0.6; text-shadow: 0 0 20px rgba(217, 54, 38, 0.6); } +} + +/* Diagonal classified stamp */ +.container::after { + content: 'EXPEDIENTE'; + position: absolute; + bottom: 15px; + right: -30px; + font-family: 'Special Elite', 'Courier Prime', monospace; + font-size: 0.7em; + letter-spacing: 3px; + color: var(--accent-danger); + opacity: 0.12; + transform: rotate(-45deg); + font-weight: 400; + white-space: nowrap; + pointer-events: none; +} + +.screen { + display: none; + animation: screenEnter 0.35s cubic-bezier(0.22, 1, 0.36, 1); + flex: 1; + overflow: hidden; + min-height: 0; +} + +.screen.active { + display: flex; + flex-direction: column; +} + +@keyframes screenEnter { + 0% { + opacity: 0; + transform: translateY(30px) scale(0.95); + filter: blur(4px); + } + 60% { + opacity: 1; + filter: blur(0); + } + 100% { + opacity: 1; + transform: translateY(0) scale(1); + filter: blur(0); + } +} + +/* Staggered children animation */ +.screen.active > * { + animation: fadeSlideUp 0.5s cubic-bezier(0.22, 1, 0.36, 1) backwards; +} + +.screen.active > *:nth-child(1) { animation-delay: 0.05s; } +.screen.active > *:nth-child(2) { animation-delay: 0.1s; } +.screen.active > *:nth-child(3) { animation-delay: 0.15s; } +.screen.active > *:nth-child(4) { animation-delay: 0.2s; } +.screen.active > *:nth-child(5) { animation-delay: 0.25s; } +.screen.active > *:nth-child(6) { animation-delay: 0.3s; } +.screen.active > *:nth-child(7) { animation-delay: 0.35s; } +.screen.active > *:nth-child(8) { animation-delay: 0.4s; } + +@keyframes fadeSlideUp { + from { + opacity: 0; + transform: translateY(15px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + FORM CONTROLS + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +.form-group { + margin-bottom: 16px; +} + +.form-group.compact { + margin-bottom: 12px; +} + +label { + display: block; + margin-bottom: 8px; + font-weight: 700; + font-size: 0.8em; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 1.2px; +} + input { width: 100%; - padding: 10px 12px; - border: 2px solid var(--border-color); - border-radius: 10px; + padding: 12px 14px; + border: 2px solid var(--border-medium); + border-radius: 0; font-size: 0.95em; - background: var(--input-bg); - color: var(--input-text); - transition: border-color 0.2s ease, background 0.3s ease; + font-family: 'JetBrains Mono', monospace; + background: var(--surface-card); + color: var(--text-primary); + transition: all 0.2s ease; + box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.1); } -input:focus { outline: none; border-color: var(--input-focus); } + +input:focus { + outline: none; + border-color: var(--accent-warning); + box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.1), 0 0 0 3px rgba(212, 165, 116, 0.2); + transform: translateY(-1px); +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + BUTTONS + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + button { width: 100%; - padding: 14px; - border: none; - border-radius: 12px; - font-size: 1em; + padding: 16px 20px; + border: 3px solid var(--text-primary); + border-radius: 0; + font-size: 0.9em; font-weight: 800; + font-family: 'JetBrains Mono', monospace; cursor: pointer; - background: linear-gradient(135deg, var(--button-gradient-start) 0%, var(--button-gradient-end) 100%); - color: white; - box-shadow: 0 4px 15px rgba(0,0,0,0.3); - transition: transform 0.15s, box-shadow 0.15s, background 0.3s ease; - margin-top: 10px; -} -button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.4); } -button:active { transform: scale(0.98); box-shadow: 0 2px 10px rgba(0,0,0,0.2); } -button.secondary { background: linear-gradient(135deg, var(--button-secondary-start) 0%, var(--button-secondary-end) 100%); color: #fff; } -button.ghost { background: var(--button-ghost-bg); color: var(--text-primary); font-weight: 600; } -.player-names-list { max-height: 280px; overflow-y: auto; margin-bottom: 8px; -webkit-overflow-scrolling: touch; } -.player-name-item { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; background: var(--card-bg); padding: 10px; border-radius: 10px; border: 1px solid var(--border-color); transition: background 0.2s ease; } -.player-name-item:hover { background: var(--card-hover); } -.player-name-item span { font-weight: 700; min-width: 76px; font-size: 0.9em; color: var(--text-secondary); } -.player-name-item input { flex: 1; padding: 8px; margin: 0; font-size: 0.9em; } -.curtain { position: relative; width: 100%; height: 280px; background: var(--curtain-bg); border-radius: 16px; overflow: hidden; margin: 12px 0; box-shadow: 0 6px 20px rgba(0,0,0,0.4); cursor: grab; user-select: none; border: 2px solid var(--border-color); transition: background 0.3s ease; } -.curtain:active { cursor: grabbing; } -.curtain-cover { position: absolute; inset: 0; background: linear-gradient(135deg, #ffa585 0%, #ffeda0 100%); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; font-size: 1.1em; font-weight: 800; color: #333; transition: transform 0.5s ease; z-index: 10; user-select: none; } -.curtain-cover.lifted { transform: translateY(-100%); } -.curtain-icon { font-size: 2.2em; } -.curtain-content { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; padding: 16px; text-align: center; } -.role { font-size: 2em; font-weight: 800; padding: 10px 20px; border-radius: 12px; text-transform: uppercase; border: 3px solid transparent; } -.role.civil { background: rgba(74, 222, 128, 0.26); color: #4ade80; border-color: #4ade80; } -.role.impostor { background: rgba(239, 68, 68, 0.28); color: #ef4444; border-color: #ef4444; } -.word { font-size: 1.7em; font-weight: 800; background: rgba(255,255,255,0.16); padding: 16px 24px; border-radius: 10px; border: 2px solid rgba(255,255,255,0.28); } -.timer { font-size: 2.8em; font-weight: 800; text-align: center; margin: 16px 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); padding: 14px; background: rgba(0,0,0,0.22); border-radius: 12px; } -.timer.warning { color: #fbbf24; animation: pulse 1s infinite; } -.timer.danger { color: #ef4444; animation: pulse 0.55s infinite; } -@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } } -.info-text { text-align: center; margin: 10px 0; font-size: 0.95em; line-height: 1.5; background: var(--info-bg); padding: 12px; border-radius: 10px; color: var(--text-secondary); border: 1px solid var(--border-color); } -.player-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 10px; margin: 12px 0; max-height: 300px; overflow-y: auto; -webkit-overflow-scrolling: touch; } -.player-item { padding: 16px 12px; background: var(--card-bg); border-radius: 12px; text-align: center; cursor: pointer; transition: transform 0.18s, background 0.18s, border 0.18s; font-weight: 700; font-size: 0.95em; border: 2px solid var(--border-color); } -.player-item:hover { background: var(--card-hover); transform: translateY(-2px); } -.player-item:active { transform: scale(0.96); } -.player-item.selected { background: var(--button-gradient-end); border-color: #fff; box-shadow: 0 0 16px rgba(245, 87, 108, 0.7); } -.player-item .vote-count { display: block; font-size: 0.85em; margin-top: 4px; opacity: 0.88; } -.results { background: rgba(255,255,255,0.16); border-radius: 10px; padding: 14px; margin: 10px 0; max-height: 400px; overflow-y: auto; -webkit-overflow-scrolling: touch; } -.role-reveal { background: rgba(0,0,0,0.3); padding: 10px; border-radius: 8px; margin: 5px 0; border-left: 4px solid transparent; font-size: 0.9em; } -.role-reveal.civil-reveal { border-left-color: #4ade80; } -.role-reveal.impostor-reveal { border-left-color: #ef4444; } -.role-reveal.executed { opacity: 0.6; background: rgba(0,0,0,0.45); } -.tag { display: inline-block; padding: 5px 8px; border-radius: 6px; background: rgba(255,255,255,0.18); margin: 3px 0; font-weight: 700; font-size: 0.85em; } -.pool-buttons { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 6px; - max-height: 200px; - overflow-y: auto; - padding: 4px 0; - -webkit-overflow-scrolling: touch; - - /* Hide scrollbar in all browsers */ - scrollbar-width: none; /* Firefox */ - -ms-overflow-style: none; /* IE/Edge */ -} -.pool-buttons::-webkit-scrollbar { - display: none; /* Chrome/Safari/Opera */ -} - -/* Wrapper for gradient effect */ -.pool-buttons-wrapper { + background: var(--text-primary); + color: var(--text-inverted); + box-shadow: var(--shadow-harsh); + transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); + margin-top: 12px; + text-transform: uppercase; + letter-spacing: 0.8px; position: relative; + overflow: hidden; + clip-path: polygon( + 0 0, + calc(100% - 12px) 0, + 100% 12px, + 100% 100%, + 12px 100%, + 0 calc(100% - 12px) + ); } -.pool-buttons-wrapper::after { + +button::before { content: ''; position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 30px; - background: linear-gradient(to bottom, transparent, rgba(102, 126, 234, 0.4)); - pointer-events: none; - border-radius: 0 0 8px 8px; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + transform: translate(-50%, -50%); + transition: width 0.5s ease, height 0.5s ease; } -.pool-btn { - padding: 10px 8px; - border-radius: 10px; - border: 2px solid var(--border-color); - background: var(--card-bg); +button:hover::before { + width: 300px; + height: 300px; +} + +button:hover { + box-shadow: 8px 8px 0px rgba(0, 0, 0, 0.3); + filter: brightness(1.1); +} + +button:active { + box-shadow: 2px 2px 0px rgba(0, 0, 0, 0.15); + filter: brightness(0.95); +} + +button.secondary { + background: linear-gradient(135deg, var(--accent-warning) 0%, #c48a2e 100%); + border-color: var(--accent-warning); + color: var(--text-inverted); + box-shadow: var(--shadow-harsh), 0 0 15px rgba(230, 167, 60, 0.25); +} + +button.ghost { + background: transparent; color: var(--text-primary); - font-weight: 700; - font-size: 0.85em; - cursor: pointer; - transition: transform 0.12s, border 0.12s, background 0.12s; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + border-color: var(--border-medium); + box-shadow: none; } -.pool-btn:hover { transform: translateY(-2px); background: var(--card-hover); } -.pool-btn.selected { border-color: var(--button-gradient-start); background: var(--button-gradient-start); color: white; box-shadow: 0 0 12px rgba(240,147,251,0.6); } -/* Welcome screen */ +button.ghost:hover { + background: var(--surface-hover); + box-shadow: var(--shadow-harsh); +} + +button:disabled { + opacity: 0.4; + cursor: not-allowed; + pointer-events: none; +} + +.btn-primary { + background: linear-gradient(135deg, var(--accent-danger) 0%, #b8301e 100%); + border-color: var(--accent-danger); + box-shadow: var(--shadow-harsh), 0 0 20px rgba(217, 54, 38, 0.3); +} + +.btn-secondary { + background: linear-gradient(135deg, var(--accent-info) 0%, #1e3a5f 100%); + border-color: var(--accent-info); + box-shadow: var(--shadow-harsh), 0 0 20px rgba(46, 78, 122, 0.3); +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + WELCOME SCREEN + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + .welcome-content { display: flex; flex-direction: column; @@ -205,7 +528,7 @@ button.ghost { background: var(--button-ghost-bg); color: var(--text-primary); f justify-content: center; text-align: center; height: 100%; - gap: 20px; + gap: 24px; padding: 20px 0; } @@ -213,31 +536,76 @@ button.ghost { background: var(--button-ghost-bg); color: var(--text-primary); f width: 140px; height: 140px; object-fit: contain; - filter: drop-shadow(0 8px 16px rgba(0,0,0,0.3)); - animation: float 3s ease-in-out infinite; + filter: drop-shadow(5px 5px 0px var(--bg-secondary)) + drop-shadow(0 0 30px rgba(230, 167, 60, 0.3)) + grayscale(0.2) contrast(1.15); + animation: logoFloat 4s ease-in-out infinite, logoGlitch 8s step-end infinite; + position: relative; } -@keyframes float { - 0%, 100% { transform: translateY(0px); } - 50% { transform: translateY(-10px); } +@keyframes logoFloat { + 0%, 100% { transform: translateY(0) rotate(0deg); } + 25% { transform: translateY(-8px) rotate(-2deg); } + 75% { transform: translateY(-8px) rotate(2deg); } +} + +@keyframes logoGlitch { + 0%, 90%, 100% { + filter: drop-shadow(4px 4px 0px var(--bg-secondary)) + drop-shadow(0 0 20px var(--border-heavy)) + grayscale(0.3) contrast(1.1); + } + 91% { + filter: drop-shadow(6px 4px 0px var(--accent-danger)) + drop-shadow(0 0 20px var(--accent-danger)) + grayscale(0) contrast(1.3); + } + 92% { + filter: drop-shadow(4px 6px 0px var(--accent-info)) + drop-shadow(0 0 20px var(--accent-info)) + grayscale(0) contrast(1.3); + } + 93% { + filter: drop-shadow(4px 4px 0px var(--bg-secondary)) + drop-shadow(0 0 20px var(--border-heavy)) + grayscale(0.3) contrast(1.1); + } } .welcome-title { - font-size: 2em; + font-size: 2.8em; margin: 0; - background: linear-gradient(135deg, var(--button-gradient-start), var(--button-gradient-end)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - font-weight: 900; - text-shadow: none; + font-family: 'Bebas Neue', 'Crimson Text', Georgia, serif; + font-weight: 400; + text-shadow: 4px 4px 0px var(--bg-secondary), 0 0 40px rgba(230, 167, 60, 0.25); + letter-spacing: 6px; + line-height: 1; + position: relative; + animation: welcomeTitleReveal 0.8s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +@keyframes welcomeTitleReveal { + 0% { + opacity: 0; + letter-spacing: 30px; + filter: blur(10px); + transform: scale(0.9); + } + 100% { + opacity: 1; + letter-spacing: 6px; + filter: blur(0); + transform: scale(1); + } } .welcome-subtitle { - font-size: 1.1em; + font-size: 0.95em; color: var(--text-secondary); margin: -10px 0 0 0; - font-weight: 500; + font-weight: 400; + letter-spacing: 0.5px; + font-family: 'JetBrains Mono', monospace; } .welcome-buttons { @@ -245,224 +613,1217 @@ button.ghost { background: var(--button-ghost-bg); color: var(--text-primary); f flex-direction: column; gap: 12px; width: 100%; - max-width: 300px; + max-width: 320px; margin-top: 10px; } .welcome-credits { color: var(--text-tertiary); - font-size: 0.85em; + font-size: 0.75em; margin-top: auto; - font-weight: 500; + font-weight: 400; + letter-spacing: 1px; + text-transform: uppercase; } -/* Rules screen */ +.welcome-credits::before { + content: 'โ”€โ”€โ”€โ”€โ”€ '; +} + +.welcome-credits::after { + content: ' โ”€โ”€โ”€โ”€โ”€'; +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + RULES SCREEN + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + .rules-content { flex: 1; overflow-y: auto; + overflow-x: hidden; padding: 10px 0; -webkit-overflow-scrolling: touch; + scrollbar-width: none; + -ms-overflow-style: none; +} + +.rules-content::-webkit-scrollbar { + display: none; } .rule-section { - background: var(--card-bg); - border: 2px solid var(--border-color); - border-radius: 12px; - padding: 16px; + background: var(--surface-card); + border: 3px solid var(--border-medium); + border-left: 8px solid var(--accent-warning); + border-radius: 0; + padding: 18px; margin-bottom: 16px; - transition: background 0.3s ease, transform 0.2s ease; + transition: all 0.3s cubic-bezier(0.22, 1, 0.36, 1); + position: relative; + box-shadow: var(--shadow-md); + clip-path: polygon( + 0 0, + 100% 0, + 100% calc(100% - 10px), + calc(100% - 10px) 100%, + 0 100% + ); + animation: ruleSlideIn 0.4s cubic-bezier(0.22, 1, 0.36, 1) backwards; +} + +.rule-section:nth-child(1) { animation-delay: 0.1s; } +.rule-section:nth-child(2) { animation-delay: 0.2s; } +.rule-section:nth-child(3) { animation-delay: 0.3s; } +.rule-section:nth-child(4) { animation-delay: 0.4s; } + +@keyframes ruleSlideIn { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.rule-section::before { + content: 'โ–ธ'; + position: absolute; + left: -3px; + top: 18px; + font-size: 1.5em; + color: var(--accent-warning); + animation: blink 2s ease-in-out infinite; +} + +@keyframes blink { + 0%, 49%, 100% { opacity: 1; } + 50%, 99% { opacity: 0; } } .rule-section:hover { - background: var(--card-hover); - transform: translateY(-2px); + background: var(--surface-hover); + border-left-color: var(--accent-danger); + box-shadow: var(--shadow-lg); } .rule-section h3 { - margin: 0 0 12px 0; + margin: 0 0 14px 0; color: var(--text-primary); - font-size: 1.1em; -} - -.rule-section p { - margin: 6px 0; - color: var(--text-secondary); - line-height: 1.6; font-size: 0.95em; } -.rule-section p:first-of-type { - margin-top: 0; +.rule-section p { + margin: 8px 0; + color: var(--text-secondary); + line-height: 1.7; + font-size: 0.85em; + letter-spacing: 0.3px; } .rule-section strong { - color: var(--button-gradient-end); + color: var(--accent-danger); font-weight: 800; -} - -/* Theme toggle button */ -.theme-toggle { - position: fixed; - top: 20px; - right: 20px; - width: 50px; - height: 50px; - border-radius: 50%; - border: 2px solid var(--border-color); - background: var(--container-bg); - backdrop-filter: blur(10px); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 1.5em; - box-shadow: 0 4px 12px var(--shadow-color); - transition: transform 0.2s ease, background 0.3s ease, border 0.3s ease; - z-index: 1000; - margin: 0; - padding: 0; -} -.theme-toggle:hover { - transform: scale(1.1) rotate(15deg); - background: var(--card-hover); -} -.theme-toggle:active { - transform: scale(0.95); -} -.theme-icon { - transition: transform 0.3s ease; - display: inline-block; -} - -/* Language toggle button */ -.language-toggle { - position: fixed; - top: 80px; - right: 20px; - width: auto; - min-width: 50px; - height: 50px; - padding: 0 15px; - border-radius: 25px; - border: 2px solid var(--border-color); - background: var(--container-bg); - backdrop-filter: blur(10px); - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - font-size: 1em; - font-weight: 600; - box-shadow: 0 4px 12px var(--shadow-color); - transition: transform 0.2s ease, background 0.3s ease, border 0.3s ease; - z-index: 1000; - color: var(--text-primary); - margin: 0; -} -.language-toggle:hover { - transform: scale(1.05); - background: var(--card-hover); -} -.language-toggle:active { - transform: scale(0.95); -} -.language-icon { - font-size: 1.2em; - transition: transform 0.3s ease; - display: inline-block; -} -.language-text { + text-transform: uppercase; font-size: 0.9em; letter-spacing: 0.5px; } -/* Exit game button */ +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + PLAYER MANAGEMENT + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +.player-names-list { + flex: 1 1 auto; + min-height: 0; + max-height: 360px; /* Altura mรกxima para activar scroll y mostrar fila parcial - efecto peek */ + overflow-y: scroll; + overflow-x: hidden; + margin-bottom: 12px; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE y Edge */ + /* Visual frame to indicate scrollable area */ + background: var(--surface-card); + border: 4px solid var(--border-heavy); + border-radius: 0; + padding: 12px; + box-shadow: inset 0 4px 12px rgba(0, 0, 0, 0.15), + inset 0 -4px 12px rgba(0, 0, 0, 0.15), + var(--shadow-md); + /* Gradiente para crear efecto peek - texto cortado visible */ + -webkit-mask-image: linear-gradient(to bottom, + black 0%, + black calc(100% - 40px), + transparent 100%); + mask-image: linear-gradient(to bottom, + black 0%, + black calc(100% - 40px), + transparent 100%); +} + +.player-names-list::-webkit-scrollbar { + display: none; /* Chrome, Safari, Opera */ +} + +.player-name-item { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 8px; + background: var(--bg-secondary); + padding: 12px; + border-radius: 0; + border: 2px solid var(--border-light); + border-left: 4px solid var(--accent-info); + transition: all 0.2s ease; + box-shadow: var(--shadow-sm); +} + +.player-name-item:last-child { + margin-bottom: 0; +} + +.player-name-item:hover { + background: var(--surface-hover); + border-left-color: var(--accent-warning); + transform: translateX(2px); +} + +.player-name-item span { + font-weight: 800; + min-width: 80px; + font-size: 0.8em; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 1px; +} + +.player-name-item input { + flex: 1; + padding: 10px; + margin: 0; + font-size: 0.85em; + border-width: 2px; +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + CURTAIN REVEAL MECHANISM + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +.curtain { + position: relative; + width: 100%; + height: 280px; + background: var(--bg-secondary); + border-radius: 0; + overflow: hidden; + margin: 12px 0; + box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.3), var(--shadow-harsh); + cursor: grab; + user-select: none; + border: 3px solid var(--border-heavy); + flex-shrink: 0; +} + +.curtain:active { + cursor: grabbing; +} + +.curtain-cover { + position: absolute; + inset: 0; + background: + repeating-linear-gradient( + 0deg, + #2a2a2a 0px, + #2a2a2a 8px, + #1a1a1a 8px, + #1a1a1a 12px + ), + linear-gradient(180deg, rgba(255,200,100,0.03) 0%, transparent 50%); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + font-size: 1.1em; + font-weight: 800; + color: #888; + transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); + z-index: 10; + user-select: none; + box-shadow: inset 0 -20px 40px rgba(0, 0, 0, 0.5), inset 0 0 60px rgba(0,0,0,0.3); + letter-spacing: 2px; + font-family: 'Bebas Neue', 'JetBrains Mono', monospace; +} + +.curtain-cover::after { + content: ''; + position: absolute; + bottom: -8px; + left: 0; + right: 0; + height: 8px; + background: linear-gradient(90deg, + transparent 0%, + rgba(0,0,0,0.3) 25%, + rgba(0,0,0,0.5) 50%, + rgba(0,0,0,0.3) 75%, + transparent 100%); +} + +.curtain-cover.lifted { + transform: translateY(-100%); +} + +.curtain-icon { + font-size: 2.5em; + animation: bounce 2s ease-in-out infinite; +} + +@keyframes bounce { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-12px); } +} + +.curtain-content { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + padding: 20px; + text-align: center; +} + +.role { + font-size: 2.4em; + font-weight: 400; + padding: 16px 32px; + border-radius: 0; + text-transform: uppercase; + border: 4px solid; + font-family: 'Bebas Neue', 'JetBrains Mono', monospace; + letter-spacing: 6px; + box-shadow: var(--shadow-harsh); + position: relative; + animation: roleReveal 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +@keyframes roleReveal { + 0% { + opacity: 0; + transform: scale(0.5) rotate(-5deg); + filter: blur(10px); + } + 50% { + transform: scale(1.1) rotate(2deg); + } + 100% { + opacity: 1; + transform: scale(1) rotate(0); + filter: blur(0); + } +} + +.role.civil { + background: var(--accent-success); + color: var(--text-inverted); + border-color: #3d5a40; + animation: civilPulse 2s ease-in-out infinite; +} + +.role.impostor { + background: var(--accent-danger); + color: var(--text-inverted); + border-color: #8a2e26; + animation: impostorPulse 1.5s ease-in-out infinite; +} + +@keyframes civilPulse { + 0%, 100% { box-shadow: 4px 4px 0px rgba(0, 0, 0, 0.3), 0 0 0px rgba(90, 125, 95, 0.5); } + 50% { box-shadow: 4px 4px 0px rgba(0, 0, 0, 0.3), 0 0 25px rgba(90, 125, 95, 0.8); } +} + +@keyframes impostorPulse { + 0%, 100% { box-shadow: 4px 4px 0px rgba(0, 0, 0, 0.3), 0 0 0px rgba(196, 69, 54, 0.5); } + 50% { box-shadow: 4px 4px 0px rgba(0, 0, 0, 0.3), 0 0 30px rgba(196, 69, 54, 0.9); } +} + +.word { + font-size: 2em; + font-weight: 400; + background: var(--surface-card); + padding: 20px 36px; + border-radius: 0; + border: 3px solid var(--border-heavy); + font-family: 'Special Elite', 'Crimson Text', serif; + letter-spacing: 2px; + box-shadow: var(--shadow-harsh); + color: var(--text-primary); + text-transform: uppercase; + animation: wordReveal 0.5s cubic-bezier(0.22, 1, 0.36, 1) 0.2s forwards; + opacity: 0; +} + +@keyframes wordReveal { + 0% { + opacity: 0; + transform: translateY(20px); + filter: blur(5px); + } + 100% { + opacity: 1; + transform: translateY(0); + filter: blur(0); + } +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + TIMER + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +.timer { + font-size: 4em; + font-weight: 800; + text-align: center; + margin: 20px 0; + padding: 24px; + background: var(--surface-card); + border-radius: 0; + border: 5px solid var(--border-heavy); + font-family: 'Bebas Neue', 'JetBrains Mono', monospace; + letter-spacing: 8px; + box-shadow: var(--shadow-harsh), inset 0 0 30px rgba(0, 0, 0, 0.2); + position: relative; + clip-path: polygon( + 16px 0, + calc(100% - 16px) 0, + 100% 16px, + 100% calc(100% - 16px), + calc(100% - 16px) 100%, + 16px 100%, + 0 calc(100% - 16px), + 0 16px + ); + animation: timerAppear 0.5s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +@keyframes timerAppear { + from { + opacity: 0; + transform: scale(0.8); + filter: blur(5px); + } + to { + opacity: 1; + transform: scale(1); + filter: blur(0); + } +} + +.timer::before { + content: ''; + position: absolute; + top: 8px; + right: 8px; + width: 12px; + height: 12px; + background: var(--accent-success); + border-radius: 50%; + box-shadow: 0 0 10px var(--accent-success); + animation: statusBlink 2s ease-in-out infinite; +} + +@keyframes statusBlink { + 0%, 49%, 100% { opacity: 1; } + 50%, 99% { opacity: 0.3; } +} + +.timer.warning { + color: var(--accent-warning); + border-color: var(--accent-warning); + animation: timerShake 0.5s ease-in-out infinite; +} + +.timer.warning::before { + background: var(--accent-warning); + box-shadow: 0 0 10px var(--accent-warning); +} + +.timer.danger { + color: var(--accent-danger); + border-color: var(--accent-danger); + animation: timerShake 0.25s ease-in-out infinite, dangerFlash 1s ease-in-out infinite; +} + +.timer.danger::before { + background: var(--accent-danger); + box-shadow: 0 0 15px var(--accent-danger); + animation: statusBlink 0.5s ease-in-out infinite; +} + +@keyframes timerShake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-4px); } + 75% { transform: translateX(4px); } +} + +@keyframes dangerFlash { + 0%, 100% { background: var(--surface-card); } + 50% { background: rgba(196, 69, 54, 0.15); } +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + INFO BOXES & CONTENT + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +.info-text { + text-align: center; + margin: 14px 0; + font-size: 0.85em; + line-height: 1.7; + background: var(--surface-card); + padding: 14px 16px; + border-radius: 0; + color: var(--text-secondary); + border: 2px solid var(--border-light); + border-left: 5px solid var(--accent-info); + box-shadow: var(--shadow-sm), inset 4px 0 8px rgba(46, 78, 122, 0.1); + letter-spacing: 0.3px; +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + PLAYER SELECTION GRID + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +.player-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 10px; + margin: 12px 0; + flex: 1; + overflow-y: auto; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + padding: 4px; + scrollbar-width: none; + -ms-overflow-style: none; +} + +.player-list::-webkit-scrollbar { + display: none; +} + +.player-item { + padding: 18px 14px; + min-height: 80px; /* Altura fija para evitar cambios de tamaรฑo con vote-count */ + background: var(--surface-card); + border-radius: 0; + text-align: center; + cursor: pointer; + transition: all 0.25s cubic-bezier(0.22, 1, 0.36, 1); + font-weight: 800; + font-size: 0.85em; + border: 3px solid var(--border-medium); + box-shadow: var(--shadow-sm); + letter-spacing: 0.5px; + text-transform: uppercase; + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + clip-path: polygon( + 8px 0, + 100% 0, + 100% calc(100% - 8px), + calc(100% - 8px) 100%, + 0 100%, + 0 8px + ); + animation: playerItemAppear 0.3s cubic-bezier(0.22, 1, 0.36, 1) backwards; +} + +.player-item:nth-child(1) { animation-delay: 0.05s; } +.player-item:nth-child(2) { animation-delay: 0.1s; } +.player-item:nth-child(3) { animation-delay: 0.15s; } +.player-item:nth-child(4) { animation-delay: 0.2s; } +.player-item:nth-child(5) { animation-delay: 0.25s; } +.player-item:nth-child(6) { animation-delay: 0.3s; } +.player-item:nth-child(7) { animation-delay: 0.35s; } +.player-item:nth-child(8) { animation-delay: 0.4s; } +.player-item:nth-child(9) { animation-delay: 0.45s; } +.player-item:nth-child(10) { animation-delay: 0.5s; } + +@keyframes playerItemAppear { + from { + opacity: 0; + transform: scale(0.8); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.player-item::before { + content: 'โ–ก'; + position: absolute; + top: 6px; + right: 6px; + font-size: 1.2em; + transition: all 0.2s ease; +} + +.player-item:hover { + background: var(--surface-hover); + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.25); + filter: brightness(1.05); +} + +.player-item:active { + box-shadow: 2px 2px 0px rgba(0, 0, 0, 0.15); + filter: brightness(0.95); +} + +.player-item.selected { + background: var(--accent-danger); + border-color: var(--text-primary); + color: var(--text-inverted); + box-shadow: 0 0 0 4px rgba(217, 54, 38, 0.5), 6px 6px 0px rgba(0, 0, 0, 0.4); + animation: selectPulse 0.3s ease-out; +} + +@keyframes selectPulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.08); } + 100% { transform: scale(1); } +} + +.player-item.selected::before { + content: 'โ˜‘'; + animation: checkAppear 0.2s ease-out; +} + +@keyframes checkAppear { + from { transform: scale(0) rotate(-180deg); } + to { transform: scale(1) rotate(0); } +} + +.player-item.disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; + background: var(--bg-secondary); + border-color: var(--border-light); + filter: grayscale(0.6); + animation: playerItemAppearDisabled 0.3s cubic-bezier(0.22, 1, 0.36, 1) backwards !important; +} + +.player-item.disabled::before { + content: 'โœ•'; + color: var(--text-tertiary); +} + +.player-item .vote-count { + display: block; + font-size: 0.7em; + margin-top: 4px; + opacity: 0.75; + font-weight: 600; + letter-spacing: 0.3px; + min-height: 1em; /* Reservar espacio siempre */ +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + RESULTS + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +.results { + background: var(--surface-card); + border-radius: 0; + padding: 14px; + margin: 8px 0; + flex: 1; + overflow: visible; + border: 2px solid var(--border-medium); + box-shadow: var(--shadow-md); + animation: resultsReveal 0.5s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +@keyframes resultsReveal { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.results h2 { + font-family: 'Bebas Neue', 'Crimson Text', Georgia, serif; + font-size: 1.6em; + letter-spacing: 3px; + margin-bottom: 10px; + animation: winnerReveal 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +@keyframes winnerReveal { + 0% { + opacity: 0; + transform: scale(0.5); + filter: blur(10px); + } + 60% { + transform: scale(1.1); + } + 100% { + opacity: 1; + transform: scale(1); + filter: blur(0); + } +} + +.role-reveal { + background: var(--bg-secondary); + padding: 8px 10px; + border-radius: 0; + margin: 5px 0; + border-left: 4px solid; + font-size: 0.8em; + letter-spacing: 0.2px; + box-shadow: var(--shadow-sm); + transition: all 0.2s ease; + animation: roleRevealSlide 0.4s cubic-bezier(0.22, 1, 0.36, 1) backwards; +} + +.role-reveal:nth-child(1) { animation-delay: 0.3s; } +.role-reveal:nth-child(2) { animation-delay: 0.4s; } +.role-reveal:nth-child(3) { animation-delay: 0.5s; } +.role-reveal:nth-child(4) { animation-delay: 0.6s; } +.role-reveal:nth-child(5) { animation-delay: 0.7s; } +.role-reveal:nth-child(6) { animation-delay: 0.8s; } +.role-reveal:nth-child(7) { animation-delay: 0.9s; } +.role-reveal:nth-child(8) { animation-delay: 1s; } +.role-reveal:nth-child(9) { animation-delay: 1.1s; } +.role-reveal:nth-child(10) { animation-delay: 1.2s; } + +@keyframes roleRevealSlide { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.role-reveal:hover { + transform: translateX(3px); +} + +.role-reveal.civil-reveal { + border-left-color: var(--accent-success); +} + +.role-reveal.impostor-reveal { + border-left-color: var(--accent-danger); +} + +.role-reveal.executed { + opacity: 0.5; + background: rgba(0, 0, 0, 0.2); + text-decoration: line-through; +} + +.tag { + display: inline-block; + padding: 6px 10px; + border-radius: 0; + background: var(--surface-hover); + margin: 4px 0; + font-weight: 800; + font-size: 0.75em; + border: 2px solid var(--border-medium); + letter-spacing: 1px; + text-transform: uppercase; +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + POOL SELECTION + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +.pool-buttons { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; + padding: 0; +} + +.pool-buttons-wrapper { + position: relative; + flex: 1 1 auto; + min-height: 0; + max-height: 320px; /* Ajustado para mostrar fila parcial - efecto peek */ + overflow-y: scroll; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE y Edge */ + /* Visual frame to indicate scrollable area */ + background: var(--surface-card); + border: 4px solid var(--border-heavy); + border-radius: 0; + padding: 12px; + margin: 12px 0; + box-shadow: inset 0 4px 12px rgba(0, 0, 0, 0.15), + inset 0 -4px 12px rgba(0, 0, 0, 0.15), + var(--shadow-md); + /* Gradiente para crear efecto peek - texto cortado visible */ + -webkit-mask-image: linear-gradient(to bottom, + black 0%, + black calc(100% - 50px), + transparent 100%); + mask-image: linear-gradient(to bottom, + black 0%, + black calc(100% - 50px), + transparent 100%); +} + +.pool-buttons-wrapper::-webkit-scrollbar { + display: none; /* Chrome, Safari, Opera */ +} + +.pool-btn { + padding: 12px 10px; + border-radius: 0; + border: 2px solid var(--border-medium); + background: var(--surface-card); + color: var(--text-primary); + font-weight: 700; + font-size: 0.8em; + cursor: pointer; + transition: all 0.18s ease; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: var(--shadow-sm); +} + +.pool-btn:hover { + background: var(--surface-hover); + box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.15); + filter: brightness(1.05); +} + +.pool-btn.selected { + border-color: var(--text-primary); + background: var(--accent-warning); + color: var(--text-inverted); + box-shadow: 0 0 0 3px rgba(212, 165, 116, 0.3), 3px 3px 0px rgba(0, 0, 0, 0.2); +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + FIXED UI CONTROLS (Theme, Language, Exit) + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +.theme-toggle { + position: fixed; + top: 20px; + right: 20px; + width: 56px; + height: 56px; + border-radius: 0; + border: 3px solid var(--border-heavy); + background: var(--surface-glass); + backdrop-filter: blur(20px); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.6em; + box-shadow: var(--shadow-harsh); + transition: all 0.2s ease; + z-index: 1000; + margin: 0; + padding: 0; +} + +.theme-toggle:hover { + box-shadow: 8px 8px 0px rgba(0, 0, 0, 0.2); + filter: brightness(1.1); +} + +.theme-toggle:active { + box-shadow: 2px 2px 0px rgba(0, 0, 0, 0.15); + filter: brightness(0.95); +} + +.theme-icon { + transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); + display: inline-block; +} + +.theme-toggle:hover .theme-icon { + transform: rotate(180deg) scale(1.1); +} + +.language-toggle { + position: fixed; + top: 86px; + right: 20px; + width: auto; + min-width: 56px; + height: 56px; + padding: 0 16px; + border-radius: 0; + border: 3px solid var(--border-heavy); + background: var(--surface-glass); + backdrop-filter: blur(20px); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 1em; + font-weight: 800; + box-shadow: var(--shadow-harsh); + transition: all 0.2s ease; + z-index: 1000; + color: var(--text-primary); + margin: 0; + text-transform: uppercase; + letter-spacing: 1px; +} + +.language-toggle:hover { + box-shadow: 8px 8px 0px rgba(0, 0, 0, 0.2); + filter: brightness(1.1); +} + +.language-toggle:active { + box-shadow: 2px 2px 0px rgba(0, 0, 0, 0.15); + filter: brightness(0.95); +} + +.language-icon { + font-size: 1.3em; + transition: transform 0.3s ease; + display: inline-block; +} + +.language-text { + font-size: 0.85em; + letter-spacing: 1.5px; + font-family: 'JetBrains Mono', monospace; +} + .exit-game { position: fixed; top: 20px; left: 20px; width: auto; - height: 50px; - padding: 0 15px; - border-radius: 25px; - border: 2px solid var(--border-color); - background: var(--container-bg); - backdrop-filter: blur(10px); + height: 56px; + padding: 0 16px; + border-radius: 0; + border: 3px solid var(--border-heavy); + background: var(--surface-glass); + backdrop-filter: blur(20px); cursor: pointer; display: none; align-items: center; justify-content: center; - gap: 8px; - font-size: 0.9em; - font-weight: 600; - box-shadow: 0 4px 12px var(--shadow-color); - transition: transform 0.2s ease, background 0.3s ease, border 0.3s ease; + gap: 10px; + font-size: 0.85em; + font-weight: 800; + box-shadow: var(--shadow-harsh); + transition: all 0.2s ease; z-index: 1000; color: var(--text-primary); + text-transform: uppercase; + letter-spacing: 1px; } + .exit-game.visible { display: inline-flex; } + .exit-game:hover { - transform: scale(1.05); - background: var(--card-hover); -} -.exit-game:active { - transform: scale(0.95); -} -.exit-icon { - font-size: 1.2em; -} -.exit-text { - font-size: 0.85em; + box-shadow: 8px 8px 0px rgba(0, 0, 0, 0.2); + background: var(--accent-danger); + color: var(--text-inverted); } -/* Mobile optimization */ +.exit-game:active { + box-shadow: 2px 2px 0px rgba(0, 0, 0, 0.15); +} + +.exit-icon { + font-size: 1.3em; +} + +.exit-text { + font-size: 0.9em; + font-family: 'JetBrains Mono', monospace; +} + +.screen-lock-toggle { + position: fixed; + top: 152px; + right: 20px; + width: 56px; + height: 56px; + border-radius: 0; + border: 3px solid var(--border-heavy); + background: var(--surface-glass); + backdrop-filter: blur(20px); + cursor: pointer; + display: none; + align-items: center; + justify-content: center; + font-size: 1.6em; + box-shadow: var(--shadow-harsh); + transition: all 0.2s ease; + z-index: 1000; + margin: 0; + padding: 0; +} + +.screen-lock-toggle.visible { + display: inline-flex; +} + +.screen-lock-toggle:hover { + box-shadow: 8px 8px 0px rgba(0, 0, 0, 0.2); + filter: brightness(1.1); +} + +.screen-lock-toggle:active { + box-shadow: 2px 2px 0px rgba(0, 0, 0, 0.15); + filter: brightness(0.95); +} + +.screen-lock-toggle.active { + background: var(--accent-success); + color: var(--text-inverted); + border-color: var(--accent-success); +} + +.screen-lock-icon { + transition: transform 0.3s ease; + display: inline-block; +} + +.screen-lock-toggle:hover .screen-lock-icon { + transform: scale(1.1); +} + +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + RESPONSIVE DESIGN + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + @media (max-width: 600px) { body { - padding: 70px 10px 10px 10px; + padding: 60px 10px 10px 10px; + font-size: 13px; } - .theme-toggle { - top: 10px; - right: 10px; - width: 45px; - height: 45px; - font-size: 1.3em; + h1 { + font-size: 1.7em; + margin-bottom: 14px; + } + + .container { + padding: 20px 16px; + } + + .theme-toggle, + .language-toggle, + .exit-game, + .screen-lock-toggle { + top: 8px; + width: 44px; + height: 44px; + min-width: 44px; } .language-toggle { - top: 65px; - right: 10px; - min-width: 45px; - height: 45px; - padding: 0 12px; - font-size: 0.9em; + top: 58px; } - .language-icon { - font-size: 1.1em; - } - - .language-text { - font-size: 0.85em; + .screen-lock-toggle { + top: 108px; } .exit-game { - top: 10px; - left: 10px; - height: 45px; padding: 0 12px; + font-size: 0.75em; + height: 44px; + } + + /* Ocultar textos en mรณvil, solo emojis */ + .exit-text, + .language-text { + display: none; + } + + .exit-game { + padding: 0; + width: 44px; + min-width: 44px; + } + + .language-toggle { + padding: 0; + width: 44px; + min-width: 44px; + } + + .exit-icon, + .language-icon { + font-size: 1.4em; + } + + .timer { + font-size: 2.5em; + padding: 16px; + } + + .welcome-title { + font-size: 1.8em; + } + + .role { + font-size: 1.6em; + padding: 10px 18px; + } + + .word { + font-size: 1.3em; + padding: 12px 20px; + } + + .form-group { + margin-bottom: 10px; + } + + .form-group.compact { + margin-bottom: 8px; + } + + button { + padding: 12px 16px; + margin-top: 8px; + } + + .rule-section { + padding: 12px; + margin-bottom: 12px; + } + + .rule-section h3 { font-size: 0.85em; + margin-bottom: 10px; } - .exit-icon { - font-size: 1.1em; - } - - .exit-text { + .rule-section p { font-size: 0.8em; + margin: 6px 0; + } + + .player-name-item { + padding: 10px; + margin-bottom: 6px; + } + + .player-name-item span { + font-size: 0.75em; + min-width: 70px; + } + + .player-item { + padding: 14px 10px; + min-height: 72px; /* Altura fija tambiรฉn en mรณvil */ + font-size: 0.8em; + } + + .pool-btn { + padding: 10px 8px; + font-size: 0.75em; + } + + .pool-buttons-wrapper { + max-height: 240px; /* Ajustado para mostrar fila parcial en mรณvil - efecto peek */ + padding: 10px; + margin: 10px 0; + } + + .player-names-list { + max-height: 280px; /* Ajustado para mostrar fila parcial en mรณvil - efecto peek */ + padding: 10px; + } + + .info-text { + padding: 12px 14px; + font-size: 0.8em; + margin: 10px 0; + } + + .curtain { + height: 240px; + margin: 10px 0; } } +/* โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + UTILITY ANIMATIONS + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” */ + +@keyframes typewriter { + from { width: 0; } + to { width: 100%; } +} + +@keyframes glitch { + 0% { transform: translate(0); } + 20% { transform: translate(-2px, 2px); } + 40% { transform: translate(-2px, -2px); } + 60% { transform: translate(2px, 2px); } + 80% { transform: translate(2px, -2px); } + 100% { transform: translate(0); } +} + +/* Smooth scrolling */ +* { + scrollbar-width: thin; + scrollbar-color: var(--border-medium) transparent; +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--border-medium); + border-radius: 0; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--border-heavy); +}