// índice de conteúdo
ÆGIS
Stack: HTML + CSS + JavaScript vanilla · Node.js + Express · Supabase (PostgreSQL + Auth) · Mistral AI (via proxy server) · Deploy no Railway.
index.html é crítica. Supabase SDK deve ser o primeiro. ranking.js deve ser o último — ele envolve o STATE num Proxy.Estado global em window.STATE (alias STATE). Única fonte de verdade da sessão. Nunca modificar métricas de jogo diretamente — use _grantXP().
Persistido via persistStateLocally() e restaurado via restoreStateFromLocal(). O ranking.js envolve o STATE num Proxy que sincroniza automaticamente com o Supabase a cada mudança relevante.
Toda alteração de score, HP, bloqueios e falhas deve passar por window._grantXP().
window._grantXP({
xp: 60, // adiciona ao score
hp: -3, // altera integridade (+cura / -dano)
blocks: 1, // incrementa bloqueios
fails: 1, // incrementa falhas
label: 'missoes:loki', // log no console (opcional)
});| Evento | XP | HP | Blq | Fal |
|---|---|---|---|---|
| Bloqueio correto do Loki | +60+lokiLevel×20 | +5% | +1 | — |
| Erro na resposta do Loki | — | −3% | — | +1 |
| Timeout no modal do Loki | — | −5% | — | +1 |
| Questão certa (cronometrado) | +10+(timeLeft/60)×20 | — | +1 | — |
| Questão certa (livre) | +10 | — | +1 | — |
| Questão errada (simulado) | — | −5% | — | +1 |
| Missão concluída | +200 | +15% | +1 | — |
| Simulado concluído (base) | +100 | — | — | — |
| Simulado acima de 80% | +50 | — | — | — |
| Flashcard revelado | — | +3% | — | — |
updateHUD() atualiza todos os elementos visuais do estado. Chamada automaticamente por _grantXP().
| Local | ID | Efeito | Cooldown |
|---|---|---|---|
| Sidebar desktop | aegisRechargeBtn | +10% HP | 2 horas |
| Footer drawer mobile | aegisRechargeBtnMob | +10% HP | 2 horas (compartilhado) |
O Loki só ataca quando lokiCanAttack() retorna true — i.e., quando existe MISSION_STATE.activeMissionId definido.
| Missão | Tema | Intervalo | Timer |
|---|---|---|---|
| 01 | Command Injection | 30–45s | 15s |
| 02 | IDOR | 22–35s | 13s |
| 03 | Broken Auth | 16–28s | 11s |
| 04 | SSRF | 12–22s | 9s |
| 05 | Supply Chain | 9–16s | 8s |
| 06 | Final APT | 7–13s | 7s |
answerLoki(i, shuffled)onTimeout(): −5% HP, +falha, reagendaPool de ataques: ATTACKS em missions-attacks.js. 70% ataques temáticos da missão ativa, 30% pool global. Taunts dinâmicos via generateLokiTaunt() no ai-router.js usando Mistral AI.
aegisHp chega a 0, todos os ataques do Loki são pausados até o jogador clicar em Reanimar.stopLokiAttacks() + limpa lokiTimeout#aegisDeathOverlayLOKI_VICTORY_LINES[]aegisHp = 50.active do overlaystartLokiAttacks()| Fonte | HP | Condição |
|---|---|---|
| Bloqueio correto do Loki | +5% | sempre |
| Missão concluída | +15% | se HP < 100 |
| Flashcard revelado | +3% | se 0 < HP < 100 |
| Recarga de emergência | +10% | cooldown 2h |
| Regeneração passiva | +2% / 30s | HP ≤ 10 e missão ativa |
| Reanimação após morte | 50% fixo | após reviveAegis() |
Cada missão tem steps com conteúdo educativo e um quiz final. MISSION_STATE (em missions-data.js) controla o passo atual.
| Missão | Tema |
|---|---|
| 01 | Command Injection |
| 02 | IDOR |
| 03 | Broken Authentication |
| 04 | SSRF |
| 05 | Supply Chain |
| 06 | Final APT |
navigate('missoes') → refreshMissionsList()goToStep(1, mission)onMissionStarted() → ativa ataques do LokigoTo(n)onMissionCompleted(id, xp) → recompensas + curaonMissionEnded() → para ataques do LokionMissionCompleted verifica completedMissions.includes(missionId) antes de conceder recompensas — evita duplicatas.Controlado por estudos.js + estudos_content.js. Inicializado via call(initEstudos). Destruído ao sair via destroyEstudos(). Módulos concluídos desbloqueiam questões extras no banco de simulados via buildSimuladoPool().
| Função | Descrição |
|---|---|
initFlashcards() | Reseta índice e renderiza primeiro card |
flipCard() | Vira o card; concede +3% HP ao revelar resposta |
nextCard() / prevCard() | Navegação circular entre os cards |
renderFlashcard() | Atualiza o DOM com o card atual |
updateFcCounter() | Atualiza "card N / total // deck: X" |
| Modo | Timer | XP por questão certa |
|---|---|---|
cronometrado | 60s por questão | 10 + (timeLeft/60) × 20 |
livre | sem timer | 10 |
buildSimuladoPool(): 12 questões base + questões de módulos concluídos, embaralhadas, 10 por sessão. Estado em window.SIMULADO_STATE, separado do STATE principal.
Inicializado por initProgresso(). Exibe XP, bloqueios e missões concluídas. A skill de Command Injection reflete o progresso real da Missão 01. A árvore de habilidades e as runas são preenchidas dinamicamente por progresso-dynamic.js.
Controlado por ranking.js. Tabs: global, semanal, missoes. Dados lidos do Supabase em tempo real via refreshRankingView(). O score é sempre calculado e validado server-side pelo server.js — o frontend não controla XP diretamente.
Breakpoint: <= 768px. Todos os componentes são injetados dinamicamente por aegis-mobile-nav.js.
| Componente | ID | Descrição |
|---|---|---|
| Topbar | #mob-topbar | Barra superior com botão ≡ e logo |
| Drawer | #mob-nav-drawer | Menu lateral com nav + stats + recarga |
| Backdrop | #mob-nav-backdrop | Overlay escuro ao abrir o drawer |
| Puxador | #mob-bot-puller | Tab lateral para abrir o ÆGIS-BOT |
window.AegisMobile.openSidebar() window.AegisMobile.closeSidebar() window.AegisMobile.openBot() window.AegisMobile.closeBot() window.AegisMobile.openDrawer() window.AegisMobile.closeDrawer() window.AegisMobile.isMob() // true se <= 768px
bind() é chamado mais de uma vez.O server.js roda em Node.js com Express e é deployado no Railway. Serve o frontend via express.static e expõe endpoints de API.
| Endpoint | Auth | Descrição |
|---|---|---|
POST /api/chat | ✓ requireAuth | Proxy para Mistral AI (ÆGIS-BOT e taunts do Loki) |
POST /api/ranking/save | ✓ requireAuth | Salva estado do jogador com score server-side |
GET /api/ranking | público | Retorna ranking global/semanal/missões |
GET /api/ranking/me | ✓ requireAuth | Retorna dados do jogador autenticado |
POST /api/mission/start | ✓ requireAuth | Gera mission_token para iniciar missão |
POST /api/mission/complete | ✓ requireAuth | Valida token e registra missão concluída |
recalculateScore() — o frontend não controla XP diretamente.fails e blocks só sobem. aegis_hp só cai. Nunca sobrescreve com valores mais favoráveis ao jogador.
Auth via OAuth Google gerenciada por nick-screen.js. Evento aegis:nick-set disparado após autenticação bem-sucedida para sincronizar o STATE. JWT validado pelo requireAuth middleware em cada request.
| Coluna | Tipo | Descrição |
|---|---|---|
user_id | text | ID do usuário Supabase |
nick | text | Nick do guardião (máx. 30 chars) |
score | int | XP total — calculado server-side |
blocks | int | Ataques bloqueados |
fails | int | Erros acumulados |
aegis_hp | int | Integridade atual |
missions | int | Número de missões concluídas |
completed_missions | int[] | IDs das missões concluídas |
Controlado por ai-router.js. Usa Mistral AI via proxy em /api/chat no server.js. Respostas locais frequentes são atendidas sem chamada à API — latência zero. Chamadas externas só ocorrem para perguntas livres e taunts dinâmicos do Loki.
| Uso | Modelo | max_tokens |
|---|---|---|
| ÆGIS-BOT (respostas livres) | mistral-small-latest | 300 |
| Loki taunts dinâmicos | mistral-small-latest | 80 |
| Reação ao resultado do ataque | mistral-small-latest | 60 |
| Função | Descrição |
|---|---|
botSay(msg) | Exibe mensagem do bot no chat |
appendMsg(html, type, cls) | Adiciona mensagem ao #chatArea |
sendUserMsg() | Lê #chatInput, tenta resposta local, depois chama Mistral |
showQR() / hideQR() | Exibe/oculta quick replies contextuais |
aegisReactToResult(win, type) | Bot reage ao resultado do ataque do Loki |
generateLokiTaunt(type, lastChoice?) | Gera fala contextual do Loki via Mistral |
getAuthToken() | Lê token do Supabase no localStorage para autenticar o proxy |
--green: #00ff41 /* cor principal */ --red: #ff1a3c /* dano / Loki */ --yellow: #ffe066 /* aviso / HP médio */ --loki-purple: #a050ff /* identidade do Loki */ --bg: #020b04 /* fundo principal */ --border: rgba(0,255,65,0.25) --border-bright:rgba(0,255,65,0.6) --text-dim: rgba(0,255,65,0.45) --text-mid: rgba(0,255,65,0.72) --text-bright: #e8ffe8 --green-faint: rgba(0,255,65,0.06) --red-glow: rgba(255,0,40,0.4) --green-glow: rgba(0,255,65,0.3)
| ID | Usado em |
|---|---|
aegisHpBar / hpText | updateHUD(), injectRechargeBtn() |
scoreDisplay / blocksDisplay / failsDisplay | updateHUD() |
lokiModal / lmBackdrop | openModal(), closeLokiModal() |
timerFill / timerCount | startTimer() |
lmChoices / lmResult / lmTaunt | openModal(), showResult() |
botAvatar / botPill / botName / botSub | disruptAegis(), recoverAegis() |
chatArea / chatInput | appendMsg(), sendUserMsg() |
aegisDeathOverlay | triggerAegisDeath(), reviveAegis() |
aegisRechargeBtn / aegisRechargeBtnMob | injectRechargeBtn(), updateRechargeBtn() |
mob-nav-drawer / mob-nav-footer | aegis-mobile-nav.js, injectRechargeBtn() |
nick-screen.js verifica sessãoranking.js restaura STATE do servidoraegis-mobile-nav.js fecha painéis, rebinda eventosinitBinBg() → updateHUD() → navigate('home') → initBot() (600ms)POST /api/mission/start → recebe mission_tokengoToStep(1, mission) → onMissionStarted() → scheduleAttack()disruptAegis() → animação + chatopenModal() → choices + timerPOST /api/mission/complete → valida token → registra no SupabaseonMissionCompleted() → XP + cura → onMissionEnded()triggerAegisDeath() → overlayreviveAegis() → HP=50 → retoma ataqueswindow.STATE). Única fonte de verdade.
ÆGIS