<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LOCKED EGO // SYSTEM</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;600;700&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.js"></script>
<style>
:root {
--primary: #00ffff;
--primary-dim: rgba(0, 255, 255, 0.1);
--secondary: #fbbf24;
--bg-dark: #050505;
--panel: #0a0f14;
--border: #1f2937;
--scanline: rgba(0, 255, 255, 0.05);
}
body {
font-family: 'Rajdhani', sans-serif;
background-color: var(--bg-dark);
color: #e0e0e0;
background-image:
linear-gradient(var(--scanline) 1px, transparent 1px),
linear-gradient(90deg, var(--scanline) 1px, transparent 1px);
background-size: 40px 40px;
overflow-x: hidden;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type=number] { -moz-appearance: textfield; }
body::after {
content: ""; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
background-size: 100% 2px, 3px 100%; pointer-events: none; z-index: 9999;
}
.cris-card { background-color: var(--panel); border: 1px solid #333; box-shadow: 0 0 10px rgba(0,0,0,0.5); position: relative; }
.cris-card::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: var(--primary); box-shadow: 0 0 10px var(--primary); }
.cris-input { background: rgba(0,0,0,0.3); border: 1px solid #333; color: var(--primary); font-family: 'Share Tech Mono', monospace; transition: all 0.3s; }
.cris-input:focus { outline: none; border-color: var(--primary); background: var(--primary-dim); box-shadow: 0 0 15px var(--primary-dim); }
.cris-btn {
background: transparent; border: 1px solid var(--primary); color: var(--primary);
font-family: 'Share Tech Mono', monospace; text-transform: uppercase;
letter-spacing: 2px; transition: all 0.2s; position: relative; overflow: hidden; cursor: pointer;
}
.cris-btn:hover { background: var(--primary); color: #000; box-shadow: 0 0 20px var(--primary); }
.cris-btn-danger { border-color: #ef4444; color: #ef4444; }
.cris-btn-danger:hover { background: #ef4444; color: #000; box-shadow: 0 0 20px #ef4444; }
.tab { clip-path: polygon(10% 0, 100% 0, 100% 100%, 0% 100%); font-family: 'Share Tech Mono', monospace; color: #666; transition: 0.3s; }
.tab.active { background: var(--primary); color: #000; font-weight: bold; }
.crit-glow { animation: pulse-gold 0.8s infinite alternate; }
@keyframes pulse-gold { from { text-shadow: 0 0 5px #ffd700; } to { text-shadow: 0 0 20px #ffd700, 0 0 10px white; } }
.dice-pop { animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
@keyframes popIn { 0% { transform: scale(0.8); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
#save-indicator { transition: opacity 0.5s; }
</style>
</head>
<body class="p-4 md:p-8 min-h-screen pb-24">
<div class="max-w-7xl mx-auto space-y-8 relative z-10">
<header class="flex flex-col md:flex-row justify-between items-end border-b-2 border-gray-800 pb-4 gap-4">
<div>
<h1 class="text-6xl font-black text-white tracking-tighter" style="text-shadow: 2px 2px 0px var(--primary);">
LOCKED<span style="color: var(--primary)">EGO</span>
</h1>
<div class="flex items-center gap-2 mt-1">
<div class="w-2 h-2 bg-green-500 rounded-full shadow-[0_0_10px_#0f0]"></div>
<p class="text-gray-500 font-mono text-xs tracking-[0.2em]">SYSTEM_VERSION_5.3 // <span class="text-green-500">SESSÃO LOCAL</span></p>
</div>
</div>
<div class="flex flex-col items-end gap-2 w-full md:w-auto">
<div id="save-indicator" class="text-xs font-mono text-yellow-500 opacity-0 mb-1">...</div>
<div class="flex gap-2">
<button id="share-btn" class="cris-btn px-4 py-1 text-xs font-bold border-cyan-400">
COMPARTILHAR FICHA
</button>
</div>
</div>
</header>
<div class="flex flex-wrap items-center justify-between gap-4 bg-black/40 p-2 border border-gray-800">
<div id="tabs-container" class="flex gap-1 overflow-x-auto pb-1"></div>
<div class="flex items-center gap-4 ml-auto">
<label class="flex items-center gap-2 text-xs font-mono text-gray-400 cursor-pointer hover:text-white">
<input type="checkbox" id="show-roll-popup-toggle" checked class="accent-cyan-500">
VISUALIZAR DADOS
</label>
<button id="edit-mode-btn" class="cris-btn px-4 py-1 text-xs border-yellow-500 text-yellow-500 hover:bg-yellow-500 hover:text-black">
<span id="edit-mode-text">MODO EDIÇÃO</span>
</button>
</div>
</div>
<main id="sheets-container" class="min-h-[500px]"></main>
</div>
<div id="modal-backdrop" class="fixed inset-0 bg-black/90 z-40 hidden backdrop-blur-sm"></div>
<div id="share-modal" class="fixed inset-0 flex items-center justify-center p-4 hidden z-50">
<div class="cris-card p-6 max-w-md w-full bg-black text-center border-cyan-500">
<h3 class="text-xl font-mono text-cyan-400 mb-2">GERAR LINK DE CONEXÃO</h3>
<p class="text-gray-400 mb-4 font-mono text-xs">Copie o link curtinho abaixo e envie para o mestre ou jogadores.</p>
<div class="flex flex-col gap-3">
<input type="text" id="share-link-input" readonly class="cris-input w-full p-3 text-center text-sm font-bold text-white" value="Gerando...">
<div class="flex gap-2 justify-center mt-2">
<button onclick="copyShareLink()" class="cris-btn px-6 py-2 bg-cyan-900/30">COPIAR LINK</button>
<button data-action="close-modal" class="cris-btn px-6 py-2 border-red-500 text-red-500 hover:bg-red-500">FECHAR</button>
</div>
</div>
</div>
</div>
<div id="dice-result-modal" class="fixed inset-0 flex items-center justify-center p-4 hidden z-50 pointer-events-none">
<div class="cris-card p-8 max-w-sm w-full text-center bg-black pointer-events-auto dice-pop border-t-4 border-t-cyan-500">
<div id="modal-content"></div>
</div>
</div>
<div id="confirm-delete-modal" class="fixed inset-0 flex items-center justify-center p-4 hidden z-50">
<div class="cris-card p-6 max-w-md w-full bg-black text-center border-red-500/50">
<h3 class="text-xl font-mono text-red-500 mb-4">PROTOCOLO DE EXCLUSÃO</h3>
<p class="text-gray-400 mb-6 font-mono text-sm">Tem certeza que deseja expurgar estes dados? Esta ação é irreversível.</p>
<div class="flex gap-4 justify-center">
<button id="confirm-delete-btn" class="cris-btn cris-btn-danger px-6 py-2">CONFIRMAR</button>
<button data-action="close-modal" class="cris-btn px-6 py-2">CANCELAR</button>
</div>
</div>
</div>
<script>
// --- DADOS DO SISTEMA LOCKED EGO ---
const POSITION_DATA = {
'Zagueiro': { choices: [{ skill: 'Marcação', value: 4 }, { skill: 'Reflexo', value: 4 }], attributeBonus: '+2 em testes contra cruzamentos', passive: '' },
'Lateral': { choices: [{ skill: 'Passe', value: 4 }, { skill: 'Arrancada', value: 4 }], attributeBonus: '', passive: '+1 quadrado extra de deslocamento' },
'Volante': { choices: [{ skill: 'Marcação', value: 4 }, { skill: 'Passe', value: 4 }], attributeBonus: '+2 em roubos de bola', passive: '' },
'Meio-Campista': { choices: [{ skill: 'Passe', value: 5 }, { skill: 'Visão', value: 5 }], attributeBonus: '+1 dado em passes longos', passive: '' },
'Meia Atacante': { choices: [{ skill: 'Drible', value: 4 }, { skill: 'Lançamento', value: 4 }], attributeBonus: '+2 em finalizações após drible', passive: '' },
'Ponta': { choices: [{ skill: 'Drible', value: 5 }, { skill: 'Lançamento', value: 5 }], attributeBonus: '+3 em dribles na linha de fundo', passive: '' },
'Atacante': { choices: [{ skill: 'Potência', value: 5 }, { skill: 'Mira', value: 5 }], attributeBonus: '+2 em chutes dentro da área', passive: '' },
'Centroavante': { choices: [{ skill: 'Potência', value: 6 }, { skill: 'Salto', value: 4 }], attributeBonus: '+3 em cabeceios', passive: '' },
};
const EGO_DATA = {
'Holístico-Liberdade': { name: 'Equação de Múltiplas Variáveis', description: '<b>Efeito:</b> Você pode gastar 5 de Fôlego para otimizar a jogada. Recebe +4 no teste de perícia, e um aliado ganha +3 na próxima ação.' },
'Individualista-Liberdade': { name: 'Dança da Liberdade', description: '<b>Efeito:</b> Recebe +6 no teste da ação se for expressão do seu prazer (ex: drible 1v1). Se bem-sucedida, reduz em 10% o custo de Ego da próxima habilidade.' },
'Holístico-Restrição': { name: 'Caçador de Coroas', description: '<b>Efeito:</b> Você ganha 3 "Cargas de Foco". Gaste uma carga antes de uma disputa direta contra sua Presa declarada para ganhar +7 naquele teste.' },
'Individualista-Restrição': { name: 'Rei da Montanha', description: '<b>Efeito:</b> O adversário que te vencer numa disputa cria um Débito. Na próxima vez contra ele, gaste 10% de Ego para ganhar +8.' }
};
const PHILOSOPHY_DATA = {
"Adaptativa": { name: "Remodular", description: "<b>Bônus:</b> +3 Visão, +2 á escolha.<br><br><b>Efeito:</b> Pode trocar os pontos de duas perícias durante a partida." },
"Analista": { name: "Percepção Tática", description: "<b>Bônus:</b> +3 Visão, +2 á escolha.<br><br><b>Efeito:</b> DTs de testes de visão diminuem em 2." },
"Cuidadosa": { name: "Auto cuidado", description: "<b>Bônus:</b> +3 Arrancada ou Drible, +2 á escolha.<br><br><b>Efeito:</b> Recebe +5 de saúde. Vida não reduz para menos do que 5." },
"Criativa": { name: "Desejo Curiosidade", description: "<b>Bônus:</b> +3 Drible, +2 Domínio.<br><br><b>Efeito:</b> Para cada jogada original, acumula +1 (máx +2) para uma ação." },
"Explosiva": { name: "Erupção de Emoções", description: "<b>Bônus:</b> +3 Marcação, +2 Arrancada.<br><br><b>Efeito:</b> Recebe 5 de fôlego fixo." },
"Heroica": { name: "Impacto Heroico", description: "<b>Bônus:</b> +3 Potência, +2 Marcação.<br><br><b>Efeito:</b> Recebe +2 contra adversários fazendo jogadas sujas." },
"Imperial": { name: "Um Rei não Cai", description: "<b>Bônus:</b> +3 Potência/Lançamento, +2 Drible/Marcação.<br><br><b>Efeito:</b> Ego não reduz para menos de 20%." },
"Irracional": { name: "Selvageria", description: "<b>Bônus:</b> +3 e +2 em duas perícias à escolha.<br><br><b>Efeito:</b> Recebe +2 num atributo e -2 em outro, permanentemente." },
"Perfeccionista": { name: "Perfeição", description: "<b>Bônus:</b> +2 em todas as perícias de um atributo específico.<br><br><b>Efeito:</b> Vence em caso de empate num atributo escolhido." },
"Protetiva": { name: "Incursão Afrontosa", description: "<b>Bônus:</b> +3 Marcação, +2 Salto.<br><br><b>Efeito:</b> Na defesa, pode dobrar seu deslocamento." },
"Suporte": { name: "Auxílio Irrestrito", description: "<b>Bônus:</b> +3 Passe, +2 Lançamento.<br><br><b>Efeito:</b> Arrancada da dupla será a maior entre os dois." }
};
const PHILOSOPHY_BONUSES = {
"Adaptativa": { "Visão": 3 }, "Analista": { "Visão": 3 }, "Criativa": { "Drible": 3, "Domínio": 2 },
"Explosiva": { "Marcação": 3, "Arrancada": 2 }, "Heroica": { "Potência": 3, "Marcação": 2 },
"Protetiva": { "Marcação": 3, "Salto": 2 }, "Suporte": { "Passe": 3, "Lançamento": 2 }
};
// --- SISTEMA CORE ---
let sheets = [];
let activeSheetIndex = 0;
let isEditMode = false;
let saveTimeout = null;
const createNewSheet = (name) => ({
id: crypto.randomUUID(),
name: name,
info: {
playerName: '',
characterName: '',
nationality: '',
team: '',
position: 'None',
value: 0,
currency: 'EUR',
positionBuffChoice: null,
egoType: 'None'
},
attributes: {
fisico: 1,
tecnica: 1,
chute: 1,
defesa: 1,
controle: 1,
carisma: 1
},
secondary: {
vigor: { current: 0, max: 0 },
folego: { current: 0, max: 0 },
ego: 0
},
skills: [
{ name: 'Arrancada', value: 0, attribute: 'fisico' },
{ name: 'Salto', value: 0, attribute: 'fisico' },
{ name: 'Lançamento', value: 0, attribute: 'tecnica' },
{ name: 'Passe', value: 0, attribute: 'tecnica' },
{ name: 'Visão', value: 0, attribute: 'tecnica' },
{ name: 'Mira', value: 0, attribute: 'chute' },
{ name: 'Potência', value: 0, attribute: 'chute' },
{ name: 'Domínio', value: 0, attribute: 'controle' },
{ name: 'Drible', value: 0, attribute: 'controle' },
{ name: 'Marcação', value: 0, attribute: 'defesa' },
{ name: 'Reflexo', value: 0, attribute: 'defesa' },
{ name: 'Diplomacia', value: 0, attribute: 'carisma' },
{ name: 'Enganação', value: 0, attribute: 'carisma' },
{ name: 'Intimidação', value: 0, attribute: 'carisma' },
{ name: 'Persuasão', value: 0, attribute: 'carisma' }
],
weapons: [],
generalAbilities: [],
styleAbilities: [],
philosophy: ''
});
// =====================================================
// CLOUDFLARE WORKER + D1
// =====================================================
const API_URL = "https://locked-ego-api.ckdgowther.workers.dev";
// =====================================================
// SALVAR FICHA LOCALMENTE
// =====================================================
const saveData = () => {
try {
localStorage.setItem(
'locked_ego_v5_local',
JSON.stringify(sheets)
);
showSaveStatus("SALVO", "text-green-500");
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
showSaveStatus("", "");
}, 2000);
} catch (e) {
console.error("Erro ao salvar localmente:", e);
showSaveStatus("ERRO AO SALVAR", "text-red-500");
}
};
// =====================================================
// INDICADOR DE SALVAMENTO
// =====================================================
const showSaveStatus = (msg, cls) => {
const el = document.getElementById('save-indicator');
if (!el) return;
el.className =
`text-[10px] font-mono font-bold ${cls} transition-opacity duration-500 ` +
(msg ? "opacity-100" : "opacity-0");
el.textContent = msg;
};
// =====================================================
// GERAR LINK PELO CLOUDFLARE
// =====================================================
// =====================================================
// GERAR LINK PELO CLOUDFLARE
// =====================================================
const generateShareLink = async () => {
const input = document.getElementById('share-link-input');
const modal = document.getElementById('share-modal');
const backdrop = document.getElementById('modal-backdrop');
const sheet = sheets[activeSheetIndex];
if (!sheet) {
input.value = "ERRO: NENHUMA FICHA SELECIONADA";
return;
}
modal.classList.remove('hidden');
backdrop.classList.remove('hidden');
input.value = "SALVANDO FICHA NO SERVIDOR...";
try {
const cleanSheet = JSON.parse(JSON.stringify(sheet));
cleanSheet.weapons =
(cleanSheet.weapons || []).filter(
w => w.name || w.description
);
cleanSheet.generalAbilities =
(cleanSheet.generalAbilities || []).filter(
w => w.name || w.description
);
cleanSheet.styleAbilities =
(cleanSheet.styleAbilities || []).filter(
w => w.name || w.description
);
const response = await fetch(`${API_URL}/api/sheets`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(cleanSheet)
});
const text = await response.text();
let data;
try {
data = JSON.parse(text);
} catch {
console.error("Resposta do Cloudflare não é JSON:", text);
throw new Error(
`Resposta inválida do servidor (${response.status})`
);
}
console.log("Resposta do Cloudflare:", data);
if (!response.ok) {
throw new Error(
data.erro ||
data.error ||
`Servidor respondeu com status ${response.status}`
);
}
if (!data.sucesso || !data.id) {
throw new Error(
data.erro ||
data.error ||
"O servidor não retornou o ID da ficha."
);
}
const shareUrl =
window.location.origin +
window.location.pathname +
'?share=' +
encodeURIComponent(data.id);
input.value = shareUrl;
} catch (e) {
console.error(
"Erro ao gerar link de compartilhamento:",
e
);
input.value = "ERRO AO GERAR LINK";
alert(
"Não foi possível gerar o link de compartilhamento.\n\n" +
e.message
);
}
};
// =====================================================
// COPIAR LINK
// =====================================================
window.copyShareLink = () => {
const input =
document.getElementById('share-link-input');
if (!input) return;
if (
!input.value ||
input.value.includes("Erro") ||
input.value.includes("erro") ||
input.value.includes("SALVANDO")
) {
return;
}
input.select();
input.setSelectionRange(0, 99999);
try {
document.execCommand('copy');
const btn =
document.querySelector('#share-modal button');
if (!btn) return;
const originalText = btn.textContent;
btn.textContent = "COPIADO!";
btn.classList.add('bg-green-600');
setTimeout(() => {
btn.textContent = originalText;
btn.classList.remove('bg-green-600');
}, 2000);
} catch (e) {
console.error("Erro ao copiar:", e);
}
};
// =====================================================
// CARREGAR FICHA DO CLOUDFLARE
// =====================================================
const checkShareFromURL = async () => {
const urlParams =
new URLSearchParams(window.location.search);
const shareId =
urlParams.get('share');
if (!shareId) {
return false;
}
try {
// CLOUDFLARE ORIGINAL
const response =
await fetch(
`${API_URL}/api/sheets/${encodeURIComponent(shareId)}`
);
if (!response.ok) {
throw new Error(
`API respondeu com status ${response.status}`
);
}
const result =
await response.json();
if (!result.sucesso || !result.data) {
throw new Error(
result.erro ||
"Ficha não encontrada."
);
}
const importedSheet = result.data;
importedSheet.id =
result.id ||
importedSheet.id ||
crypto.randomUUID();
sheets.push(importedSheet);
activeSheetIndex =
sheets.length - 1;
saveData();
window.history.replaceState(
{},
document.title,
window.location.pathname
);
return true;
} catch (e) {
console.error(
"Erro ao carregar ficha do Cloudflare:",
e
);
alert(
"Não foi possível carregar a ficha compartilhada."
);
return false;
}
};
// =====================================================
// IMPORTAÇÃO ANTIGA ?data=
// =====================================================
const checkImportFromURL = () => {
const urlParams =
new URLSearchParams(window.location.search);
const dataParam =
urlParams.get('data');
if (!dataParam) {
return false;
}
try {
const decompressed =
LZString.decompressFromEncodedURIComponent(
dataParam
);
if (!decompressed) {
return false;
}
const importedSheet =
JSON.parse(decompressed);
importedSheet.id =
crypto.randomUUID();
sheets.push(importedSheet);
activeSheetIndex =
sheets.length - 1;
saveData();
window.history.replaceState(
{},
document.title,
window.location.pathname
);
return true;
} catch (e) {
console.error(
"Erro ao importar ficha antiga:",
e
);
return false;
}
};
// =====================================================
// CARREGAR DADOS
// =====================================================
const loadData = async () => {
try {
const local =
localStorage.getItem(
'locked_ego_v5_local'
);
if (local) {
const loaded =
JSON.parse(local);
if (
Array.isArray(loaded) &&
loaded.length > 0
) {
sheets = loaded;
}
}
} catch (e) {
console.error(
"Erro ao carregar fichas locais:",
e
);
sheets = [];
}
// Se não existir nenhuma ficha,
// cria a primeira automaticamente.
if (
!Array.isArray(sheets) ||
sheets.length === 0
) {
sheets = [
createNewSheet('Ficha 1')
];
}
// Verifica se o link atual é uma ficha do Cloudflare.
const importedFromCloudflare =
await checkShareFromURL();
// Se não for Cloudflare, tenta o formato antigo.
if (!importedFromCloudflare) {
checkImportFromURL();
}
if (
activeSheetIndex < 0 ||
activeSheetIndex >= sheets.length
) {
activeSheetIndex = 0;
}
render();
};
// IMPORTAÇÃO VIA URL
const render = () => {
const container = document.getElementById('sheets-container');
const tabsContainer = document.getElementById('tabs-container');
tabsContainer.innerHTML = '';
const addBtn = document.createElement('button');
addBtn.className = 'cris-btn px-4 py-1 font-bold text-cyan-400 border-cyan-800 hover:border-cyan-400 text-lg leading-none';
addBtn.textContent = '+';
addBtn.addEventListener('click', () => {
sheets.push(createNewSheet(`Ficha ${sheets.length + 1}`));
activeSheetIndex = sheets.length - 1;
saveData(); render();
});
tabsContainer.appendChild(addBtn);
sheets.forEach((sheet, index) => {
const tab = document.createElement('button');
tab.className = `tab px-6 py-2 text-sm uppercase tracking-wider border-r border-gray-800 ${index === activeSheetIndex ? 'active' : 'bg-gray-900/50 hover:bg-gray-800'}`;
tab.textContent = sheet.name || `Ficha ${index+1}`;
tab.onclick = () => { activeSheetIndex = index; render(); };
tabsContainer.appendChild(tab);
});
const sheet = sheets[activeSheetIndex];
if(!sheet) return;
const getSkillTotal = (skill) => {
let total = skill.value || 0;
const pos = POSITION_DATA[sheet.info.position];
if(pos?.choices && sheet.info.positionBuffChoice === skill.name) {
const choice = pos.choices.find(c => c.skill === skill.name);
if(choice) total += choice.value;
}
const phil = sheet.philosophy;
if(phil && PHILOSOPHY_BONUSES[phil] && PHILOSOPHY_BONUSES[phil][skill.name]) total += PHILOSOPHY_BONUSES[phil][skill.name];
return total;
};
const html = `
<div class="space-y-6 fade-in pb-20">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="cris-card p-6">
<div class="flex justify-between items-center mb-6 border-b border-gray-800 pb-2">
<h2 class="text-xl font-bold text-cyan-400 font-mono tracking-widest">DADOS PESSOAIS</h2>
${isEditMode ? `<button data-action="delete-sheet" class="text-xs text-red-500 hover:text-red-400 font-mono uppercase">Apagar Ficha</button>` : ''}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
${inp('Nome da Ficha', 'name', sheet.name)}
${inp('Personagem', 'info.characterName', sheet.info.characterName)}
${inp('Jogador', 'info.playerName', sheet.info.playerName)}
${inp('Nacionalidade', 'info.nationality', sheet.info.nationality)}
<div class="space-y-1">
<label class="text-xs text-gray-500 uppercase tracking-widest font-mono">Tipo de Ego</label>
<select data-path="info.egoType" class="cris-input w-full p-2 text-sm">
<option value="None">Selecione...</option>
${Object.keys(EGO_DATA).map(k => `<option value="${k}" ${sheet.info.egoType === k ? 'selected' : ''}>${k}</option>`).join('')}
</select>
</div>
<div class="space-y-1">
<label class="text-xs text-gray-500 uppercase tracking-widest font-mono">Posição</label>
<select data-path="info.position" class="cris-input w-full p-2 text-sm">
<option value="None">-</option>
${Object.keys(POSITION_DATA).map(k => `<option value="${k}" ${sheet.info.position === k ? 'selected' : ''}>${k}</option>`).join('')}
</select>
</div>
${renderBuffChoice(sheet)}
</div>
</div>
<div class="cris-card p-6">
<h2 class="text-xl font-bold text-yellow-400 font-mono tracking-widest mb-6 border-b border-gray-800 pb-2">STATUS VITAIS</h2>
<div class="grid grid-cols-3 gap-4 text-center">
${stat('VIGOR', 'secondary.vigor', sheet.secondary.vigor, 'text-yellow-600')}
${stat('FÔLEGO', 'secondary.folego', sheet.secondary.folego, 'text-blue-600')}
<div class="p-3 bg-black/40 border border-gray-800">
<label class="text-xs text-purple-600 font-bold block mb-2 font-mono">EGO</label>
<input type="number" data-path="secondary.ego" value="${sheet.secondary.ego}" class="cris-input w-full text-center text-xl font-bold p-1">
</div>
</div>
${renderPositionDesc(sheet)}
</div>
</div>
<div class="cris-card p-6">
<h2 class="text-xl font-bold text-green-400 font-mono tracking-widest mb-6 border-b border-gray-800 pb-2">ATRIBUTOS</h2>
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-4">
${Object.keys(sheet.attributes).map(k => `
<div class="p-3 bg-black/40 border border-gray-800 text-center hover:border-green-500/30 transition-colors group cursor-pointer"
${!isEditMode ? `data-action="roll-attr" data-key="${k}"` : ''}>
<label class="block text-xs text-gray-400 uppercase tracking-widest mb-2 font-bold group-hover:text-green-400 transition-colors">${k}</label>
${isEditMode ?
`<input type="number" data-path="attributes.${k}" value="${sheet.attributes[k]}" class="cris-input w-full bg-transparent text-center text-3xl font-black text-yellow-400 font-mono focus:outline-none border-b border-gray-700">` :
`<div class="text-3xl font-black text-white font-mono">${sheet.attributes[k]}</div>`
}
</div>
`).join('')}
</div>
</div>
<div class="cris-card p-6">
<div class="flex justify-between items-center mb-6 border-b border-gray-800 pb-2">
<h2 class="text-xl font-bold text-purple-400 font-mono tracking-widest">PERÍCIAS</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
${['fisico','tecnica','chute','defesa','controle','carisma'].map(attr => {
const list = sheet.skills.map((s,i)=>({...s, i})).filter(s => s.attribute === attr);
if(list.length === 0) return '';
return `<div class="space-y-1">
<h3 class="text-xs font-bold text-gray-600 uppercase border-b border-gray-800 pb-1 mb-2 font-mono">${attr}</h3>
${list.map(s => {
const total = getSkillTotal(s);
const isBuffed = total > s.value;
return `<div class="flex justify-between items-center bg-gray-900/40 p-2 border border-transparent hover:border-gray-700 transition-colors group cursor-pointer" ${!isEditMode ? `data-action="roll-skill" data-idx="${s.i}"` : ''}>
<span class="text-sm font-mono text-gray-300 group-hover:text-cyan-400">${s.name}</span>
<div class="flex items-center gap-2">
${isEditMode ?
`<input type="number" class="w-12 text-center bg-black border border-gray-700 text-white text-xs p-1 font-mono" data-path="skills.${s.i}.value" value="${s.value}">`
: `<span class="text-lg font-bold font-mono ${isBuffed ? 'text-green-400' : 'text-white'}">${total}</span>`
}
</div>
</div>`
}).join('')}
</div>`;
}).join('')}
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="cris-card p-6">
<h2 class="text-xl font-bold text-cyan-400 font-mono tracking-widest mb-4 border-b border-gray-800 pb-2">FILOSOFIA</h2>
<select data-path="philosophy" class="cris-input w-full p-3 text-sm mb-4">
<option value="">Selecione...</option>
${Object.keys(PHILOSOPHY_DATA).map(p => `<option value="${p}" ${sheet.philosophy === p ? 'selected' : ''}>${p}</option>`).join('')}
</select>
${sheet.philosophy && PHILOSOPHY_DATA[sheet.philosophy] ? `
<div class="mt-4 pt-4 border-t border-gray-800">
<h3 class="text-lg font-bold text-white font-mono mb-2">${PHILOSOPHY_DATA[sheet.philosophy].name}</h3>
<div class="text-sm text-gray-400 font-mono leading-relaxed bg-black/30 p-3 border border-gray-800">
${PHILOSOPHY_DATA[sheet.philosophy].description}
</div>
</div>` : ''}
</div>
${sheet.info.egoType !== 'None' && EGO_DATA[sheet.info.egoType] ? `
<div class="cris-card p-6 border-yellow-500/30">
<h2 class="text-xl font-bold text-yellow-400 font-mono tracking-widest mb-4 border-b border-gray-800 pb-2">EGO: ${EGO_DATA[sheet.info.egoType].name}</h2>
<div class="text-sm text-gray-400 font-mono leading-relaxed bg-black/30 p-3 border border-gray-800">
${EGO_DATA[sheet.info.egoType].description}
</div>
</div>` : ''}
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
${renderList('ARMAS', 'weapons')}
${renderList('HABILIDADES GERAIS', 'generalAbilities')}
</div>
${renderList('HABILIDADES DE ESTILO', 'styleAbilities', 'lg:col-span-2')}
</div>`;
container.innerHTML = html;
// CORREÇÃO DO FOCO MANTIDA
document.querySelectorAll('input[data-path], select[data-path], textarea[data-path]').forEach(el => {
el.oninput = (e) => {
const path = e.target.dataset.path;
const val = e.target.type === 'number' ? parseInt(e.target.value)||0 : e.target.value;
let obj = sheet; const parts = path.split('.');
for(let i=0; i<parts.length-1; i++) obj = obj[parts[i]];
obj[parts[parts.length-1]] = val;
if(path === 'philosophy' || path === 'info.position') {
render();
}
else if (path === 'name') {
const tabs = document.querySelectorAll('.tab');
if(tabs[activeSheetIndex]) tabs[activeSheetIndex].textContent = val || `Ficha ${activeSheetIndex + 1}`;
}
clearTimeout(saveTimeout); saveTimeout = setTimeout(() => saveData(), 1000);
};
});
};
// --- Helpers UI ---
const inp = (l, p, v) => `<div class="space-y-1"><label class="text-xs text-gray-500 uppercase tracking-widest font-mono">${l}</label><input type="text" data-path="${p}" value="${v}" class="cris-input w-full p-2 text-sm"></div>`;
const stat = (l, p, v, c) => `<div class="p-3 bg-black/40 border border-gray-800"><label class="text-xs ${c} font-bold block mb-2 font-mono">${l}</label><div class="flex items-center justify-center gap-1 font-mono"><input type="number" data-path="${p}.current" value="${v.current}" class="cris-input w-12 text-center text-lg font-bold p-1"><span class="text-gray-600">/</span><input type="number" data-path="${p}.max" value="${v.max}" class="cris-input w-12 text-center text-sm text-gray-500 p-1 bg-transparent border-transparent"></div></div>`;
const renderList = (title, key, css='') => {
const items = sheets[activeSheetIndex][key] || [];
return `<div class="cris-card p-6 ${css}">
<div class="flex justify-between items-center mb-4 border-b border-gray-800 pb-2">
<h2 class="text-xl font-bold text-orange-400 font-mono tracking-widest">${title}</h2>
${isEditMode ? `<button data-action="add-item" data-key="${key}" class="text-xs border border-orange-500 text-orange-400 px-2 py-1 hover:bg-orange-900/20 font-mono">+</button>` : ''}
</div>
<div class="space-y-3">
${items.map((item, i) => `
<div class="bg-gray-900/30 border-l-2 border-orange-500/50 p-3 relative group hover:bg-gray-900/50 transition-colors">
${isEditMode ? `
<input type="text" data-path="${key}.${i}.name" value="${item.name}" class="font-bold bg-transparent w-full text-white mb-1 block font-mono text-sm border-b border-gray-700 focus:outline-none focus:border-orange-500 p-0">
<textarea data-path="${key}.${i}.description" class="w-full text-xs bg-transparent text-gray-400 resize-y min-h-[40px] font-mono border-none focus:outline-none p-0 mt-2">${item.description}</textarea>
<button class="absolute top-2 right-2 text-red-500 opacity-0 group-hover:opacity-100 font-bold" data-action="remove-item" data-key="${key}" data-idx="${i}">✕</button>
` : `
<h4 class="font-bold text-white mb-1 font-mono text-sm">${item.name}</h4>
<p class="text-xs text-gray-400 font-mono whitespace-pre-wrap">${item.description}</p>
`}
</div>
`).join('')}
${items.length === 0 ? '<p class="text-center text-gray-700 text-xs font-mono italic">Nenhum registro.</p>' : ''}
</div>
</div>`;
};
const renderBuffChoice = (sheet) => { const pos = POSITION_DATA[sheet.info.position]; if (!pos?.choices) return ''; return `<div class="md:col-span-2 mt-2 p-3 bg-gray-900/30 border border-dashed border-gray-700"><h4 class="text-xs font-bold text-gray-400 uppercase mb-2 text-center font-mono">Bônus de Posição</h4><div class="flex justify-center gap-4 flex-wrap">${pos.choices.map(c => `<label class="flex items-center gap-2 cursor-pointer text-xs font-mono text-gray-300"><input type="radio" name="position-buff" value="${c.skill}" ${sheet.info.positionBuffChoice === c.skill ? 'checked' : ''} onchange="sheets[activeSheetIndex].info.positionBuffChoice='${c.skill}';render();saveData();" class="accent-cyan-500"> +${c.value} ${c.skill}</label>`).join('')}</div></div>`; };
const renderPositionDesc = (sheet) => { const pos = POSITION_DATA[sheet.info.position]; if (!pos) return ''; return `<div class="mt-4 pt-2 border-t border-gray-800 text-center font-mono text-xs">${pos.attributeBonus ? `<p class="text-green-400 mb-1">${pos.attributeBonus}</p>` : ''}${pos.passive ? `<p class="text-blue-400">${pos.passive}</p>` : ''}</div>`; };
// --- SISTEMA DE DADOS ---
const doRoll = (attrDiceCount, bonus, title) => {
let count = parseInt(attrDiceCount) || 1; if(count < 1) count = 1;
const rolls = [];
const buffer = new Uint32Array(count); crypto.getRandomValues(buffer);
for(let i=0; i<count; i++) rolls.push((buffer[i]%20)+1);
rolls.sort((a,b) => b-a);
const total = rolls[0] + parseInt(bonus || 0);
if(document.getElementById('show-roll-popup-toggle').checked) {
const m = document.getElementById('dice-result-modal'); const c = document.getElementById('modal-content');
m.classList.remove('hidden');
let frames = 0;
const interval = setInterval(() => {
frames++;
const fake = rolls.map(() => Math.floor(Math.random()*20)+1).sort((a,b)=>b-a);
c.innerHTML = `
<h3 class="text-gray-500 title-font text-xs mb-2 tracking-widest font-mono uppercase">${title}</h3>
<div class="text-7xl font-black text-gray-600 title-font mb-4 italic">${fake[0]+bonus}</div>
<div class="text-sm font-mono text-gray-500">[ ${fake.join(' | ')} ] + ${bonus}</div>
`;
if(frames > 8) {
clearInterval(interval);
const rHtml = rolls.map((r,i) => {
let cl = 'text-gray-500';
if(r===20) cl = 'text-yellow-400 crit-glow font-black text-2xl';
else if(r>=15) cl = 'text-green-400 font-bold';
else if(r===1) cl = 'text-red-600 font-bold';
else if(i===0) cl = 'text-white font-bold border-b-2 border-cyan-500';
return `<span class="${cl} mx-1">${r}</span>`;
}).join('<span class="text-gray-700">|</span>');
c.innerHTML = `
<h3 class="text-cyan-400 title-font text-sm mb-2 tracking-widest font-mono uppercase">${title}</h3>
<div class="text-7xl font-black text-white title-font mb-4 italic drop-shadow-lg" style="text-shadow: 0 0 15px var(--primary);">${total}</div>
<div class="text-xs font-mono text-gray-300 bg-gray-900 border border-gray-700 p-3 rounded inline-block shadow-inner mt-2">DADOS: [ ${rHtml} ] <br><span class="text-cyan-500 mt-2 block font-bold">+ ${bonus} BÔNUS</span></div>
<button class="mt-6 cris-btn w-full py-2" onclick="this.closest('#dice-result-modal').classList.add('hidden')"><span>FECHAR</span></button>
`;
}
}, 40);
}
};
// --- EVENTOS ---
document.body.addEventListener('click', (e) => {
const t = e.target.closest('[data-action]'); if(!t) return;
const action = t.dataset.action; const sheet = sheets[activeSheetIndex];
if(action === 'roll-attr') doRoll(sheet.attributes[t.dataset.key], 0, `ATRIBUTO: ${t.dataset.key}`);
if(action === 'roll-skill') {
const s = sheet.skills[parseInt(t.dataset.idx)];
let bonus = s.value || 0;
if(sheet.info.positionBuffChoice === s.name) bonus += (POSITION_DATA[sheet.info.position]?.choices?.find(c=>c.skill===s.name)?.value || 0);
if(PHILOSOPHY_BONUSES[sheet.philosophy]?.[s.name]) bonus += PHILOSOPHY_BONUSES[sheet.philosophy][s.name];
doRoll(sheet.attributes[s.attribute], bonus, `PERÍCIA: ${s.name}`);
}
if(action === 'add-item') { sheet[t.dataset.key].push({name:'Novo', description:''}); saveData(); render(); }
if(action === 'remove-item') { sheet[t.dataset.key].splice(t.dataset.idx, 1); saveData(); render(); }
if (action === 'close-modal') {
document.querySelectorAll('.fixed.z-50').forEach(el => {
el.classList.add('hidden');
});
document.getElementById('modal-backdrop')?.classList.add('hidden');
}
});
document.getElementById('edit-mode-btn').onclick = () => {
isEditMode = !isEditMode;
document.getElementById('edit-mode-text').textContent =
isEditMode ? 'JOGANDO' : 'MODO EDIÇÃO';
render();
};
document.getElementById('share-btn').onclick = generateShareLink;
document.getElementById('confirm-delete-btn').onclick = () => { sheets.splice(activeSheetIndex, 1); if(sheets.length===0) sheets.push(createNewSheet('Ficha 1')); activeSheetIndex = 0; saveData(); document.getElementById('confirm-delete-modal').classList.add('hidden'); render(); };
// --- BOOT ---
loadData();
</script>
</body>
</html>