chore: update asset versions [skip ci]
This commit is contained in:
@@ -1,468 +0,0 @@
|
||||
const STORAGE_KEY = 'impostorGameStateV2';
|
||||
const MAX_PLAYERS = 10;
|
||||
const MIN_PLAYERS = 3;
|
||||
const POOLS_CACHE_KEY = 'impostorWordPoolsV1';
|
||||
const POOLS_MANIFEST_URL = 'word-pools/manifest.json';
|
||||
|
||||
const EMBEDDED_POOLS = [
|
||||
{ id: 'animales_naturaleza', name: 'Animales y naturaleza', emoji: '🌿', words: ['Perro','Gato','Lobo','Zorro','Oso','Tigre','León','Pantera','Jaguar','Puma','Guepardo','Elefante','Rinoceronte','Hipopótamo','Jirafa','Cebra','Camello','Dromedario','Canguro','Koala','Panda','Mapache','Nutria','Castor','Foca','Morsa','Delfín','Ballena','Tiburón','Orca','Pulpo','Calamar','Medusa','Tortuga','Lagarto','Cocodrilo','Serpiente','Anaconda','Iguana','Rana','Sapo','Búho','Halcón','Águila','Cóndor','Gaviota','Loro','Flamenco','Pingüino','Avestruz','Gallina','Pato','Ganso','Cisne','Abeja','Hormiga','Mariquita','Libélula','Mariposa','Escarabajo','Grillo','Saltamontes','Araña','Escorpión','Lombriz','Caracol','Estrella de mar','Coral','Musgo','Helecho','Pino','Roble','Encina','Palmera','Cactus','Bambú','Rosa','Tulipán','Girasol','Lavanda','Montaña','Río','Lago','Mar','Playa','Desierto','Selva','Bosque','Pradera','Glaciar','Volcán'] },
|
||||
{ id: 'vida_cotidiana', name: 'Vida cotidiana', emoji: '🏠', words: ['Pan','Leche','Café','Té','Agua','Jugo','Refresco','Cerveza','Vino','Pizza','Hamburguesa','Sándwich','Taco','Burrito','Pasta','Arroz','Paella','Sushi','Ramen','Ensalada','Sopa','Croqueta','Tortilla','Empanada','Arepa','Queso','Jamón','Chorizo','Pollo','Carne','Cerdo','Pescado','Marisco','Patata','Tomate','Cebolla','Ajo','Pimiento','Zanahoria','Lechuga','Brócoli','Coliflor','Manzana','Plátano','Naranja','Pera','Uva','Fresa','Mango','Piña','Melón','Sandía','Yogur','Galletas','Chocolate','Helado','Cereales','Mantequilla','Aceite','Sal','Pimienta','Azúcar','Harina','Huevo','Cuchara','Tenedor','Cuchillo','Plato','Vaso','Taza','Olla','Sartén','Microondas','Horno','Nevera','Mesa','Silla','Sofá','Cama','Almohada','Sábana','Toalla','Ducha','Jabón','Champú','Cepillo','Pasta de dientes'] },
|
||||
{ id: 'deportes', name: 'Deportes', emoji: '🏅', words: ['Fútbol','Baloncesto','Tenis','Pádel','Bádminton','Voleibol','Béisbol','Rugby','Hockey hielo','Hockey césped','Golf','Boxeo','MMA','Judo','Karate','Taekwondo','Esgrima','Tiro con arco','Halterofilia','Crossfit','Atletismo','Maratón','Triatlón','Ciclismo ruta','Ciclismo montaña','BMX','Natación','Waterpolo','Surf','Vela','Remo','Piragüismo','Esquí','Snowboard','Patinaje artístico','Patinaje velocidad','Curling','Escalada','Senderismo','Trail running','Parkour','Gimnasia artística','Gimnasia rítmica','Trampolín','Skate','Breakdance','Carreras coches','Fórmula 1','Rally','Karting','Motociclismo','Enduro','Motocross','Equitación','Polo','Críquet','Billar','Dardos','Petanca','Pickleball','Ultimate frisbee','Paintball','Airsoft','eSports'] },
|
||||
{ id: 'marcas', name: 'Marcas', emoji: '🛍️', words: ['Apple','Samsung','Google','Microsoft','Amazon','Meta','Tesla','Toyota','Honda','Ford','BMW','Mercedes','Audi','Volkswagen','Porsche','Ferrari','Lamborghini','Maserati','McLaren','Chevrolet','Nissan','Kia','Hyundai','Peugeot','Renault','Volvo','Jaguar','Land Rover','Fiat','Alfa Romeo','Ducati','Yamaha','Canon','Nikon','Sony','Panasonic','LG','Philips','Siemens','Bosch','Whirlpool','Ikea','Zara','H&M','Uniqlo','Nike','Adidas','Puma','Reebok','New Balance','Under Armour','Converse','Vans','Patagonia','The North Face','Columbia','Levi’s','Calvin Klein','Gucci','Prada','Louis Vuitton','Chanel','Hermès','Dior','Rolex','Omega','Casio','Pepsi','Coca-Cola','Fanta','Red Bull','Monster','Starbucks','Nespresso','Nestlé','Danone','Kellogg’s','Oreo','Intel','AMD','Nvidia','Qualcomm','TikTok','Netflix','Disney','Warner Bros','HBO','Spotify','Airbnb','Uber','Booking'] },
|
||||
{ id: 'musica', name: 'Música', emoji: '🎵', words: ['Guitarra','Piano','Violín','Batería','Bajo','Saxofón','Trompeta','Flauta','Clarinete','Acordeón','Ukelele','Arpa','Sintetizador','DJ','Micrófono','Altavoz','Concierto','Festival','Vinilo','Rock','Pop','Punk','Metal','Heavy','Thrash','Death metal','Jazz','Blues','Soul','Funk','R&B','Rap','Hip hop','Trap','Reggaetón','Salsa','Bachata','Merengue','Cumbia','Vallenato','Flamenco','Rumba','Bossa nova','Samba','Tango','Country','EDM','Techno','House','Trance','Dubstep','Drum and bass','Lo-fi','Reggae','Ska','K-pop','J-pop','Indie','Gospel','Ópera','Sinfonía','Orquesta','Coro','Cantautor','Balada','Bolero','Ranchera','Corrido','Mariachi'] },
|
||||
{ id: 'personajes', name: 'Personajes', emoji: '🧙', words: ['Sherlock Holmes','Harry Potter','Hermione Granger','Ron Weasley','Albus Dumbledore','Voldemort','Frodo Bolsón','Sam Gamyi','Gandalf','Aragorn','Legolas','Gimli','Gollum','Bilbo Bolsón','Katniss Everdeen','Peeta Mellark','Batman','Bruce Wayne','Joker','Harley Quinn','Superman','Clark Kent','Lois Lane','Wonder Woman','Diana Prince','Flash','Barry Allen','Aquaman','Arthur Curry','Spider-Man','Peter Parker','Iron Man','Tony Stark','Capitán América','Steve Rogers','Black Widow','Natasha Romanoff','Hulk','Bruce Banner','Thor','Loki','Thanos','Doctor Strange','Wanda Maximoff','Vision','Star-Lord','Gamora','Groot','Rocket','Drax','Deadpool','Wolverine','Magneto','Professor X','Storm','Cyclops','Jean Grey','Mystique','Darth Vader','Luke Skywalker','Leia Organa','Han Solo','Chewbacca','Yoda','Obi-Wan Kenobi','Anakin Skywalker','Rey','Kylo Ren','R2-D2','C-3PO','Indiana Jones','Lara Croft','James Bond','Mario','Luigi','Princesa Peach','Bowser','Link','Zelda','Geralt de Rivia','Ciri','Yennefer','Kratos','Atreus','Ellie','Joel Miller','Nathan Drake','Master Chief','Cortana','Sonic','Tails','Ash Ketchum','Pikachu','Goku','Vegeta','Naruto','Sasuke','Luffy','Zoro','Nami','Tanjiro','Nezuko','Saitama','Light Yagami','L Lawliet'] }
|
||||
];
|
||||
|
||||
let availablePools = [];
|
||||
let poolsCache = {};
|
||||
|
||||
let state = {
|
||||
phase: 'setup',
|
||||
numPlayers: 6,
|
||||
numImpostors: 1,
|
||||
gameTime: 180,
|
||||
deliberationTime: 60,
|
||||
playerNames: [],
|
||||
roles: [],
|
||||
civilianWord: '',
|
||||
impostorWord: '',
|
||||
currentReveal: 0,
|
||||
startPlayer: 0,
|
||||
turnDirection: 'horario',
|
||||
revealOrder: [],
|
||||
timerEndAt: null,
|
||||
timerPhase: null,
|
||||
votes: {},
|
||||
votingPlayer: 0,
|
||||
selections: [],
|
||||
executed: [],
|
||||
selectedPool: 'animales_naturaleza',
|
||||
votingPool: null,
|
||||
isTiebreak: false,
|
||||
tiebreakCandidates: []
|
||||
};
|
||||
|
||||
const saveState = () => localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||
const loadState = () => {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return false;
|
||||
try { state = JSON.parse(raw); return true; } catch { return false; }
|
||||
};
|
||||
const clearState = () => localStorage.removeItem(STORAGE_KEY);
|
||||
|
||||
const loadPoolsCache = () => {
|
||||
try { poolsCache = JSON.parse(localStorage.getItem(POOLS_CACHE_KEY) || '{}'); } catch { poolsCache = {}; }
|
||||
};
|
||||
const savePoolsCache = () => localStorage.setItem(POOLS_CACHE_KEY, JSON.stringify(poolsCache));
|
||||
|
||||
// ---------- Defaults ----------
|
||||
function defaultImpostors(nPlayers) {
|
||||
const capped = Math.min(Math.max(nPlayers, MIN_PLAYERS), MAX_PLAYERS);
|
||||
let impostors = 1;
|
||||
if (capped > 7) impostors = 3;
|
||||
else if (capped > 5) impostors = 2;
|
||||
const halfCap = Math.max(1, Math.floor(capped / 2));
|
||||
return Math.min(impostors, halfCap);
|
||||
}
|
||||
|
||||
function defaultGameTime(nPlayers) {
|
||||
const capped = Math.min(Math.max(nPlayers, MIN_PLAYERS), MAX_PLAYERS);
|
||||
if (capped <= 4) return 300;
|
||||
if (capped >= 10) return 900;
|
||||
const extraPlayers = capped - 4;
|
||||
const seconds = 300 + extraPlayers * 100;
|
||||
return Math.round(seconds / 30) * 30;
|
||||
}
|
||||
|
||||
function defaultDeliberation(gameSeconds) {
|
||||
return Math.max(30, Math.round(gameSeconds / 3));
|
||||
}
|
||||
|
||||
// ---------- Pools ----------
|
||||
async function loadPoolsList() {
|
||||
loadPoolsCache();
|
||||
let list = [];
|
||||
try {
|
||||
const res = await fetch(POOLS_MANIFEST_URL);
|
||||
if (res.ok) list = await res.json();
|
||||
} catch (_) {}
|
||||
if (!Array.isArray(list) || list.length === 0) {
|
||||
list = EMBEDDED_POOLS.map(p => ({ id: p.id, name: p.name, emoji: p.emoji, count: p.words.length }));
|
||||
}
|
||||
availablePools = list;
|
||||
renderPoolButtons();
|
||||
}
|
||||
|
||||
function parseWordsFile(text) {
|
||||
const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
||||
if (!lines.length) return [];
|
||||
if (lines[0].startsWith('#')) return lines.slice(1);
|
||||
return lines;
|
||||
}
|
||||
|
||||
async function pickWords() {
|
||||
const poolId = state.selectedPool || 'default';
|
||||
let words = [];
|
||||
if (poolsCache[poolId]?.words) {
|
||||
words = poolsCache[poolId].words;
|
||||
} else if (poolId !== 'default') {
|
||||
const res = await fetch(`word-pools/${poolId}.txt`);
|
||||
if (!res.ok) throw new Error('No se pudo cargar el pool');
|
||||
const text = await res.text();
|
||||
words = parseWordsFile(text);
|
||||
poolsCache[poolId] = { words, ts: Date.now() }; savePoolsCache();
|
||||
} else {
|
||||
words = EMBEDDED_POOLS[0].words;
|
||||
}
|
||||
const shuffled = [...words].sort(() => Math.random() - 0.5);
|
||||
return { civilian: shuffled[0], impostor: shuffled[1] };
|
||||
}
|
||||
|
||||
function renderPoolButtons() {
|
||||
const container = document.getElementById('pool-buttons');
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
availablePools.forEach(pool => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'pool-btn';
|
||||
btn.textContent = `${pool.emoji || '🎲'} ${pool.name || pool.id}`;
|
||||
if (state.selectedPool === pool.id) btn.classList.add('selected');
|
||||
btn.onclick = () => { state.selectedPool = pool.id; saveState(); renderPoolButtons(); };
|
||||
container.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Configuración y nombres ----------
|
||||
function goToNames() {
|
||||
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));
|
||||
let nImpostors = parseInt(document.getElementById('num-impostors').value) || defaultImpostors(nPlayers);
|
||||
nImpostors = Math.min(Math.max(1, nImpostors), maxImpostors);
|
||||
let gTime = parseInt(document.getElementById('game-time').value) || defaultGameTime(nPlayers);
|
||||
gTime = Math.min(Math.max(gTime, 60), 900);
|
||||
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('Impostores debe ser menor que jugadores'); return; }
|
||||
state.numPlayers = nPlayers; state.numImpostors = nImpostors; state.gameTime = gTime; state.deliberationTime = dTime;
|
||||
buildNameInputs();
|
||||
showScreen('names-screen');
|
||||
}
|
||||
|
||||
function buildNameInputs() {
|
||||
const list = document.getElementById('player-names-list');
|
||||
list.innerHTML = '';
|
||||
for (let i = 0; i < state.numPlayers; i++) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'player-name-item';
|
||||
div.innerHTML = `<span>Jugador ${i+1}:</span><input id="player-name-${i}" value="${state.playerNames[i] || 'Jugador '+(i+1)}" />`;
|
||||
list.appendChild(div);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Inicio de partida ----------
|
||||
function startGame() {
|
||||
state.playerNames = [];
|
||||
for (let i = 0; i < state.numPlayers; i++) {
|
||||
const val = document.getElementById(`player-name-${i}`).value.trim();
|
||||
state.playerNames.push(val || `Jugador ${i+1}`);
|
||||
}
|
||||
pickWords().then(({civilian, impostor}) => {
|
||||
state.civilianWord = civilian;
|
||||
state.impostorWord = impostor;
|
||||
finalizeStart();
|
||||
}).catch(() => {
|
||||
const fallback = EMBEDDED_POOLS[0].words;
|
||||
const shuffled = [...fallback].sort(() => Math.random() - 0.5);
|
||||
state.civilianWord = shuffled[0];
|
||||
state.impostorWord = shuffled[1];
|
||||
finalizeStart();
|
||||
});
|
||||
}
|
||||
|
||||
function finalizeStart() {
|
||||
state.roles = Array(state.numPlayers - state.numImpostors).fill('CIVIL').concat(Array(state.numImpostors).fill('IMPOSTOR')).sort(() => Math.random()-0.5);
|
||||
state.startPlayer = Math.floor(Math.random() * state.numPlayers);
|
||||
state.turnDirection = Math.random() < 0.5 ? 'horario' : 'antihorario';
|
||||
const step = state.turnDirection === 'horario' ? 1 : -1;
|
||||
state.revealOrder = Array.from({length: state.numPlayers}, (_, k) => (state.startPlayer + step * k + state.numPlayers) % state.numPlayers);
|
||||
state.currentReveal = 0; state.phase = 'pre-reveal'; state.votes = {}; state.votingPlayer = 0; state.selections = []; state.executed = []; state.timerEndAt = null; state.timerPhase = null;
|
||||
state.votingPool = null; state.isTiebreak = false; state.tiebreakCandidates = [];
|
||||
saveState();
|
||||
renderSummary();
|
||||
showScreen('pre-reveal-screen');
|
||||
}
|
||||
|
||||
// Ajustar defaults cuando se edita el nº de jugadores
|
||||
document.getElementById('num-players').addEventListener('change', () => {
|
||||
let nPlayers = parseInt(document.getElementById('num-players').value) || MIN_PLAYERS;
|
||||
nPlayers = Math.min(Math.max(nPlayers, MIN_PLAYERS), MAX_PLAYERS);
|
||||
document.getElementById('num-players').value = nPlayers;
|
||||
const imp = defaultImpostors(nPlayers);
|
||||
const gTime = defaultGameTime(nPlayers);
|
||||
const dTime = defaultDeliberation(gTime);
|
||||
document.getElementById('num-impostors').max = Math.max(1, Math.floor(nPlayers / 2));
|
||||
document.getElementById('num-impostors').value = imp;
|
||||
document.getElementById('game-time').value = gTime;
|
||||
document.getElementById('deliberation-time').value = dTime;
|
||||
});
|
||||
|
||||
function renderSummary() {
|
||||
const el = document.getElementById('config-summary');
|
||||
const fmt = secs => `${Math.floor(secs/60)}:${(secs%60).toString().padStart(2,'0')}`;
|
||||
const startName = state.playerNames[state.startPlayer] || `Jugador ${state.startPlayer+1}`;
|
||||
const poolMeta = availablePools.find(p => p.id === state.selectedPool) || EMBEDDED_POOLS[0];
|
||||
el.innerHTML = `
|
||||
<p><strong>Jugadores:</strong> ${state.numPlayers}</p>
|
||||
<p><strong>Impostores:</strong> ${state.numImpostors}</p>
|
||||
<p><strong>Tiempo de partida:</strong> ${fmt(state.gameTime)}</p>
|
||||
<p><strong>Tiempo de deliberación:</strong> ${fmt(state.deliberationTime)}</p>
|
||||
<p><strong>Pool:</strong> ${poolMeta.emoji || '🎲'} ${poolMeta.name || poolMeta.id}</p>
|
||||
<p><strong>Empieza:</strong> ${startName} · <strong>Orden:</strong> ${state.turnDirection === 'horario' ? 'Horario' : 'Antihorario'}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
// ---------- Revelación ----------
|
||||
function loadCurrentReveal() {
|
||||
state.phase = 'reveal'; saveState();
|
||||
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);
|
||||
}
|
||||
const idx = state.revealOrder[state.currentReveal];
|
||||
const name = state.playerNames[idx];
|
||||
document.getElementById('current-player-name').textContent = name;
|
||||
document.getElementById('curtain-cover').classList.remove('lifted');
|
||||
document.getElementById('next-player-btn').style.display = 'none';
|
||||
document.getElementById('start-game-btn').style.display = 'none';
|
||||
}
|
||||
|
||||
function liftCurtain() {
|
||||
const cover = document.getElementById('curtain-cover');
|
||||
if (cover.classList.contains('lifted')) return;
|
||||
cover.classList.add('lifted');
|
||||
const idx = state.revealOrder[state.currentReveal];
|
||||
const role = state.roles[idx];
|
||||
const word = role === 'CIVIL' ? state.civilianWord : state.impostorWord;
|
||||
document.getElementById('role-text').textContent = role;
|
||||
document.getElementById('role-text').className = 'role ' + (role === 'CIVIL' ? 'civil' : 'impostor');
|
||||
document.getElementById('word-text').textContent = word;
|
||||
setTimeout(() => {
|
||||
if (state.currentReveal + 1 < state.numPlayers) document.getElementById('next-player-btn').style.display = 'block';
|
||||
else document.getElementById('start-game-btn').style.display = 'block';
|
||||
}, 700);
|
||||
}
|
||||
|
||||
function nextReveal() { state.currentReveal++; saveState(); loadCurrentReveal(); }
|
||||
|
||||
// swipe support
|
||||
(() => {
|
||||
const curtain = document.getElementById('curtain');
|
||||
let startY = null;
|
||||
curtain.addEventListener('touchstart', e => { startY = e.touches[0].clientY; }, {passive:true});
|
||||
curtain.addEventListener('touchmove', e => { if (startY === null) return; const dy = e.touches[0].clientY - startY; if (dy < -40) { liftCurtain(); startY = null; } }, {passive:true});
|
||||
curtain.addEventListener('click', liftCurtain);
|
||||
})();
|
||||
|
||||
// ---------- Timers ----------
|
||||
let timerInterval = null;
|
||||
function startPhaseTimer(phase, seconds, elementId, onEnd) {
|
||||
if (timerInterval) clearInterval(timerInterval);
|
||||
const now = Date.now();
|
||||
state.timerPhase = phase;
|
||||
state.timerEndAt = now + seconds*1000;
|
||||
saveState();
|
||||
const el = document.getElementById(elementId);
|
||||
const tick = () => {
|
||||
const remaining = Math.max(0, Math.round((state.timerEndAt - Date.now())/1000));
|
||||
updateTimerDisplay(el, remaining);
|
||||
if (remaining <= 0) { clearInterval(timerInterval); playBeep(); onEnd(); }
|
||||
};
|
||||
tick();
|
||||
timerInterval = setInterval(tick, 1000);
|
||||
}
|
||||
|
||||
function resumeTimerIfNeeded() {
|
||||
if (!state.timerEndAt || !state.timerPhase) return;
|
||||
const remaining = Math.round((state.timerEndAt - Date.now())/1000);
|
||||
if (remaining <= 0) { state.timerEndAt = null; saveState(); return; }
|
||||
if (state.timerPhase === 'game') { showScreen('game-screen'); startPhaseTimer('game', remaining, 'game-timer', startDeliberationPhase); }
|
||||
else if (state.timerPhase === 'deliberation') { showScreen('deliberation-screen'); startPhaseTimer('deliberation', remaining, 'deliberation-timer', startVotingPhase); }
|
||||
}
|
||||
|
||||
function updateTimerDisplay(el, remaining) {
|
||||
const minutes = Math.floor(remaining/60); const secs = remaining%60;
|
||||
el.textContent = `${minutes}:${secs.toString().padStart(2,'0')}`;
|
||||
el.className = 'timer';
|
||||
if (remaining <= 10) el.classList.add('danger'); else if (remaining <= 30) el.classList.add('warning');
|
||||
}
|
||||
|
||||
function playBeep() {
|
||||
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);
|
||||
}
|
||||
|
||||
// ---------- Fases ----------
|
||||
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) {
|
||||
state.phase = 'voting';
|
||||
state.votingPlayer = 0;
|
||||
state.votes = {};
|
||||
state.selections = [];
|
||||
state.votingPool = candidates;
|
||||
state.isTiebreak = isTiebreak;
|
||||
saveState();
|
||||
renderVoting();
|
||||
showScreen('voting-screen');
|
||||
}
|
||||
function skipToDeliberation() { if (timerInterval) clearInterval(timerInterval); startDeliberationPhase(); }
|
||||
function skipToVoting() { if (timerInterval) clearInterval(timerInterval); startVotingPhase(); }
|
||||
function startTiebreakDeliberation(candidates) {
|
||||
state.phase = 'deliberation';
|
||||
state.tiebreakCandidates = candidates;
|
||||
saveState();
|
||||
showScreen('deliberation-screen');
|
||||
startPhaseTimer('deliberation', 60, 'deliberation-timer', () => startVotingPhase(candidates, true));
|
||||
}
|
||||
|
||||
// ---------- Votación secreta ----------
|
||||
function renderVoting() {
|
||||
const pool = state.votingPool || Array.from({length: state.numPlayers}, (_, i) => i);
|
||||
const voter = state.playerNames[state.votingPlayer];
|
||||
document.getElementById('voter-name').textContent = voter;
|
||||
document.getElementById('votes-needed').textContent = state.numImpostors;
|
||||
state.selections = state.selections || [];
|
||||
const list = document.getElementById('vote-list'); list.innerHTML = '';
|
||||
pool.forEach(i => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'player-item';
|
||||
item.textContent = state.playerNames[i];
|
||||
if (state.votes[i]) item.innerHTML += `<span class="vote-count">Votos: ${state.votes[i]}</span>`;
|
||||
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);
|
||||
}
|
||||
list.appendChild(item);
|
||||
});
|
||||
updateConfirmButton();
|
||||
}
|
||||
|
||||
function toggleSelection(idx, el) {
|
||||
if (idx === state.votingPlayer) return;
|
||||
if (state.selections.includes(idx)) state.selections = state.selections.filter(x => x !== idx);
|
||||
else {
|
||||
if (state.selections.length >= state.numImpostors) return;
|
||||
state.selections.push(idx);
|
||||
}
|
||||
saveState();
|
||||
renderVoting();
|
||||
}
|
||||
|
||||
function updateConfirmButton() {
|
||||
const btn = document.getElementById('confirm-vote-btn');
|
||||
btn.disabled = state.selections.length !== state.numImpostors;
|
||||
}
|
||||
|
||||
function confirmCurrentVote() {
|
||||
state.selections.forEach(t => { state.votes[t] = (state.votes[t] || 0) + 1; });
|
||||
state.votingPlayer++;
|
||||
state.selections = [];
|
||||
saveState();
|
||||
if (state.votingPlayer >= state.numPlayers) { handleVoteOutcome(); return; }
|
||||
renderVoting();
|
||||
}
|
||||
|
||||
// ---------- Resolución de voto ----------
|
||||
function handleVoteOutcome() {
|
||||
const pool = state.votingPool || Array.from({length: state.numPlayers}, (_, i) => i);
|
||||
const counts = pool.map(idx => ({ idx, votes: state.votes[idx] || 0 }));
|
||||
counts.sort((a, b) => b.votes - a.votes);
|
||||
|
||||
let slots = state.numImpostors;
|
||||
const executed = [];
|
||||
for (let i = 0; i < counts.length && slots > 0; ) {
|
||||
const currentVotes = counts[i].votes;
|
||||
const group = [];
|
||||
let j = i;
|
||||
while (j < counts.length && counts[j].votes === currentVotes) { group.push(counts[j].idx); j++; }
|
||||
if (group.length <= slots) {
|
||||
executed.push(...group);
|
||||
slots -= group.length;
|
||||
i = j;
|
||||
} else {
|
||||
// Tie for remaining slots
|
||||
if (state.isTiebreak) {
|
||||
// segunda vez empatados: ganan impostores
|
||||
state.executed = [];
|
||||
showResults(true);
|
||||
return;
|
||||
}
|
||||
startTiebreakDeliberation(group);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
state.executed = executed;
|
||||
showResults();
|
||||
}
|
||||
|
||||
// ---------- Resultados ----------
|
||||
function showResults(isTiebreak = false) {
|
||||
state.phase = 'results'; saveState();
|
||||
const executed = state.executed || [];
|
||||
let impostorsAlive = 0;
|
||||
state.roles.forEach((r,i) => { if (r === 'IMPOSTOR' && !executed.includes(i)) impostorsAlive++; });
|
||||
const winner = impostorsAlive > 0 ? 'IMPOSTORES' : 'CIVILES';
|
||||
const results = document.getElementById('results-content');
|
||||
results.innerHTML = `
|
||||
<h2>${winner === 'CIVILES' ? '✅ ¡GANAN LOS CIVILES!' : '❌ ¡GANAN LOS IMPOSTORES!'}</h2>
|
||||
<p><strong>Ejecutados:</strong> ${executed.length ? executed.map(i => state.playerNames[i]).join(', ') : 'Nadie'}</p>
|
||||
<p><strong>Votos:</strong> ${Object.keys(state.votes).length ? '' : 'Sin votos'}</p>
|
||||
<h3 style="margin-top:18px;">Roles revelados</h3>
|
||||
${state.roles.map((role,i) => {
|
||||
const word = role === 'CIVIL' ? state.civilianWord : state.impostorWord;
|
||||
const killed = executed.includes(i) ? 'executed' : '';
|
||||
return `<div class="role-reveal ${role === 'CIVIL' ? 'civil-reveal' : 'impostor-reveal'} ${killed}"><strong>${state.playerNames[i]}:</strong> ${role} — "${word}" ${killed ? '☠️' : ''}</div>`;
|
||||
}).join('')}
|
||||
`;
|
||||
showScreen('results-screen');
|
||||
}
|
||||
|
||||
// ---------- Utilidades ----------
|
||||
function showScreen(id) {
|
||||
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
|
||||
document.getElementById(id).classList.add('active');
|
||||
state.phase = id.replace('-screen','');
|
||||
saveState();
|
||||
}
|
||||
|
||||
function newMatch() { clearState(); state = { ...state, phase:'setup', timerEndAt:null, timerPhase:null, votingPool:null, isTiebreak:false, tiebreakCandidates:[] }; location.reload(); }
|
||||
|
||||
// ---------- Rehidratación ----------
|
||||
(function init() {
|
||||
const restored = loadState();
|
||||
showScreen('setup-screen');
|
||||
loadPoolsList();
|
||||
if (!state.turnDirection) state.turnDirection = 'horario';
|
||||
if (typeof state.startPlayer !== 'number') state.startPlayer = 0;
|
||||
switch (state.phase) {
|
||||
case 'setup': showScreen('setup-screen'); break;
|
||||
case 'names': buildNameInputs(); showScreen('names-screen'); break;
|
||||
case 'pre-reveal': renderSummary(); showScreen('pre-reveal-screen'); break;
|
||||
case 'reveal': showScreen('reveal-screen'); loadCurrentReveal(); break;
|
||||
case 'game': showScreen('game-screen'); resumeTimerIfNeeded(); break;
|
||||
case 'deliberation': showScreen('deliberation-screen'); resumeTimerIfNeeded(); break;
|
||||
case 'voting': showScreen('voting-screen'); renderVoting(); break;
|
||||
case 'results': showResults(); break;
|
||||
default: showScreen('setup-screen');
|
||||
}
|
||||
})();
|
||||
1347
script.f88d8968.js
Normal file
1347
script.f88d8968.js
Normal file
File diff suppressed because it is too large
Load Diff
1829
styles.1a37b506.css
Normal file
1829
styles.1a37b506.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,455 +0,0 @@
|
||||
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
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: 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;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
body {
|
||||
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: center;
|
||||
padding: 70px 16px 16px;
|
||||
color: var(--text-primary);
|
||||
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(--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: 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%
|
||||
);
|
||||
}
|
||||
|
||||
.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: 12px 14px;
|
||||
border: 2px solid var(--border-medium);
|
||||
border-radius: 0;
|
||||
font-size: 0.95em;
|
||||
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(--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: 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: 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)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user