desenvolvimento pre-lancamento

Commit inicial - add do repo privado para o repo NT

style: changes header's logo and colors

style: changes home page first session layout

feat: creates about us home page section

chore: creates home page section for whom

chore: creates student materails home page section

chore: creates teachers materials home page section

chore: creates teacher materials home page section

style: changes primary color

style: changes color at activities page

style: changes about page color

style: changes name to Decoda

fix: changes route to about page at footer

fix: changes background color

style: changes game page header colors

style: changes footer colors

chore: adds home page sections title

style: changes main font family to Lato

style: adds title font

fix: changes sizes to be more responsive for mobile

ajuste no build vercel

atualiza regras envio homol

Adiciona instrucoes de uso

add JupyterLite

fix solucao turtle

Add Mole Mash e Modal de Falhas

Add Progress Bar na pagina de Atividades

fix game name

chore: atualiza lockfile removendo vercel analytics

inclusão de efeito ao mudar de fase

add mecanismo de solução de fases em debug

vite config test

add BaseGame e refator do MoleMash

refatoração turtle

refatoração automato

refatoração automato

add tag

bug 1 e 2 automato

mostrar apenas games em homologação na pagina de atividades

aumentar timeout das fases finais do Turtle

fix bug scroll

add video

refactor semaforo

arrumar ordem das cores

add build docs

update vercel

update vercel

update vercel

update vercel

update vercel

add vercel jupyter

add vercel jupyter

fix deploy Vercel

fix deploy Vercel

fix deploy Vercel

add cripto

add cripto

refatoração

fix tour Mole Mash

.

remover arquivos de controle

chore: adds development tag for activity card

remover arquivos de status indevidamente versionados

atualizar cores nas atividades

add Quebra Cabeças

add Quebra Cabeças

add iniciativas

add Iniciativas

alteração de fotos pesadas

fix menu mobile

fix menu mobile

fix menu mobile

add Aspirador

update icons

update identidade visual documentação

update jupyter

add kernel python local

add kernel python local

add kernel python local

feat: add health check

feat: add primeiros passos

add letramento

mover letramento de lugar

update path games

update path games

fix: ajuste clique rapido no botão executar

remover dead code

fix: refactor: extract shared utilities for storage, phase unlock and mobile detection

stabilize context references and fix stale closure

extrair GameProgressContext do GameStateContext (SRP)

refactor(game): extrair usePhaser e useGameModals de GameBase + corrigir bugs descobertos

refactor(game): remove todos os aliases PT/EN duplicados

Remover aliases PT/EN da camada de modais

refactor + tests

security: add CodeSanitizer and integrate into GameInterpreter

- CodeSanitizer.js: 4 built-in rules (max_length, infinite_while,
  infinite_for, excessive_nesting) with pluggable extra rules
- GameInterpreter.executeCode: calls sanitizeCode() before js-interpreter,
  differentiates CodeSanitizationError (warn) from other errors (error)
- 21 unit tests for CodeSanitizer (100% coverage)
- 4 integration tests in GameInterpreter for sanitization paths

add CodeSanitizer

fix: fase 10 aspirador

fix: bug semaforo

teste

feat: add version

Ajusta a landing page para ficar mais próxima ao protótipo

ajusta raio da borda do botão de Acesse nosso Laboratório

pequenos ajustes de layout na página de iniciativas

atualiza tabela de jogos educativos com os jogos disponíveis atualmente

ajustados pequenos detalhes e informações do jogos na seção de guias pedagógicos

troca nome playground para laboratório e adiciona imagens do lab

adiciona documentação de conceitos básicos de programação

ajustado pequenos erros de digitação

adiciona tooltip com conceitos escondidos em hover na tag +N de conceitos

update docs dev

desativar tour

setup matriz MoleMash

setup matriz MoleMash

fix: link

update version

update docs

update docs

mudou o layout de quem somos

mudei as imgs dos icons e baixei o botao

centraliza titulo com imagem e ajusta sessão com gradiente vermelho-rosa

adiciona responsividade para a pagina quem somos

ajusta botão de conheça nossa história

ajustes

ajustes na home + add. teclado

update version

security

security

feat: add tapume para telas pequenas

v1.1.0

feat: decoda offline

feat: doc offline

offline

fix: ajustes para release

fix: navbar; config ordenação; versão

fix: rotas docs e jupyter para pwa

delete private files

Co-authored-by: Indra Araujo <indra.araujo.santos@gmail.com>
Co-authored-by: solange dos santos <sollangelive71@gmail.com>
This commit is contained in:
2025-10-29 21:30:14 -03:00
committed by Graciano
parent e24ee22b5a
commit 3da7d323e8
577 changed files with 121994 additions and 158 deletions

View File

@@ -0,0 +1,92 @@
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
function notify(type, payload = {}) {
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
}
const arena = document.getElementById('arena');
const banner = document.getElementById('successBanner');
let filesPlaced = 0;
const TOTAL_FILES = 6;
notify('started');
// Create drop zone (folder)
const dropZone = document.createElement('div');
dropZone.className = 'absolute bottom-10 right-10 w-48 h-48 bg-yellow-100 border-4 border-yellow-300 rounded-xl flex flex-col items-center justify-center transition-all';
dropZone.innerHTML = `
<i data-lucide="folder" class="w-24 h-24 text-yellow-600 mb-2"></i>
<p class="text-lg font-bold text-gray-800">Meus Arquivos</p>
`;
arena.appendChild(dropZone);
lucide.createIcons();
// Create draggable files
const fileTypes = [
{ icon: 'file-text', color: 'red', name: 'Texto.txt' },
{ icon: 'image', color: 'purple', name: 'Foto.jpg' },
{ icon: 'music', color: 'green', name: 'Musica.mp3' },
{ icon: 'video', color: 'blue', name: 'Video.mp4' },
{ icon: 'file-code', color: 'orange', name: 'Codigo.js' },
{ icon: 'file-archive', color: 'gray', name: 'Arquivo.zip' },
];
fileTypes.forEach((file, i) => {
createDraggableFile(file, i);
});
function createDraggableFile(file, index) {
const fileEl = document.createElement('div');
fileEl.className = `absolute w-24 h-24 bg-white border-2 border-gray-300 rounded-lg flex flex-col items-center justify-center cursor-grab shadow-lg transition-all hover:shadow-xl`;
const row = Math.floor(index / 3);
const col = index % 3;
fileEl.style.left = `${50 + col * 110}px`;
fileEl.style.top = `${50 + row * 110}px`;
fileEl.innerHTML = `
<i data-lucide="${file.icon}" class="w-8 h-8 text-${file.color}-500 mb-1"></i>
<p class="text-xs font-semibold text-gray-700">${file.name}</p>
`;
fileEl.draggable = true;
arena.appendChild(fileEl);
lucide.createIcons();
fileEl.addEventListener('dragstart', (e) => {
fileEl.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
});
fileEl.addEventListener('dragend', () => {
fileEl.classList.remove('dragging');
});
}
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('ring-4', 'ring-green-400', 'scale-105');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('ring-4', 'ring-green-400', 'scale-105');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('ring-4', 'ring-green-400', 'scale-105');
const dragging = document.querySelector('.dragging');
if (dragging) {
dragging.remove();
filesPlaced++;
notify('running', { step: filesPlaced });
if (filesPlaced >= TOTAL_FILES) {
arena.classList.add('hidden');
banner.classList.remove('hidden');
lucide.createIcons();
notify('success', { score: 100 });
notify('completed', { score: 100 });
}
}
});

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse - Arrastar</title>
<link rel="stylesheet" href="../../shared/letramento.css">
<script src="../../shared/lucide.js"></script>
<style>
body { overflow: hidden; height: 100vh; width: 100vw; }
.dragging { opacity: 0.5; cursor: grabbing !important; }
</style>
</head>
<body class="bg-gradient-to-br from-brand-50 to-gray-100 flex items-center justify-center h-screen w-screen overflow-hidden p-4">
<div class="w-full h-full flex flex-col">
<div class="bg-white/95 backdrop-blur-sm border border-white/30 rounded-xl shadow-xl flex-1 flex flex-col overflow-hidden">
<div class="flex items-center justify-center gap-4 p-4 border-b border-white/20">
<i data-lucide="move" class="w-12 h-12 text-red-600"></i>
<h2 class="text-2xl font-bold text-gray-800">Arraste os arquivos para a pasta</h2>
</div>
<div class="flex-1 p-6 overflow-hidden">
<div id="arena" class="relative h-full bg-white/50 rounded-xl border-2 border-gray-200 shadow-inner overflow-hidden"></div>
<div id="successBanner" class="hidden mt-6 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-8 flex items-center justify-center gap-4">
<i data-lucide="check-circle" class="text-green-600 w-16 h-16"></i>
<div>
<h2 class="text-4xl font-bold text-green-700 mb-2">Muito bem!</h2>
<p class="text-xl text-gray-700">Você aprendeu a arrastar!</p>
</div>
</div>
</div>
</div>
</div>
<script src="./activity.js"></script>
<script>lucide.createIcons();</script>
</body>
</html>

View File

@@ -0,0 +1,212 @@
// ═══════════════════════════════════════════════════════════════════════
// Mouse Basic Activity - Logic
// ═══════════════════════════════════════════════════════════════════════
// ── postMessage helpers ──────────────────────────────────────────────
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
function notify(type, payload = {}) {
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
}
// ── DOM Elements ─────────────────────────────────────────────────────
const arena = document.getElementById('arena');
const instrEl = document.getElementById('instruction');
const hintEl = document.getElementById('hint');
const coverWrap = document.getElementById('coverageWrap');
const coverBar = document.getElementById('coverageBar');
const progLabel = document.getElementById('progressLabel');
const banner = document.getElementById('successBanner');
const successMsg = document.getElementById('successMsg');
// ── State ────────────────────────────────────────────────────────────
const TOTAL_STEPS = 3;
let currentStep = 0;
let started = false;
const steps = [
{
instruction: 'Passo 1 de 3: Mova o mouse pela área abaixo',
hint: 'Explore toda a área movendo o mouse. Preencha pelo menos 60% dela.',
setup: setupMoveStep,
},
{
instruction: 'Passo 2 de 3: Clique no botão',
hint: 'Mova o cursor até o botão e clique uma vez.',
setup: setupClickStep,
},
{
instruction: 'Passo 3 de 3: Dê um duplo clique no botão',
hint: 'Dois cliques rápidos sobre o botão!',
setup: setupDblClickStep,
},
];
// ── Rendering ────────────────────────────────────────────────────────
function renderStep(step) {
const s = steps[step];
instrEl.textContent = s.instruction;
hintEl.textContent = s.hint;
clearArena();
s.setup();
}
function clearArena() {
arena.innerHTML = '';
arena.style.cursor = 'default';
}
function nextStep(feedbackMsg) {
if (!started) {
notify('started', { step: currentStep + 1 });
started = true;
}
notify('running', { step: currentStep + 1, message: feedbackMsg });
currentStep++;
setTimeout(() => {
if (currentStep >= TOTAL_STEPS) {
finishActivity();
} else {
renderStep(currentStep);
}
}, 1200);
}
function finishActivity() {
instrEl.textContent = 'Você completou todos os passos!';
hintEl.textContent = '';
clearArena();
arena.classList.add('hidden');
coverWrap.classList.add('hidden');
successMsg.textContent = 'Agora você sabe como mover e clicar com o mouse. Excelente trabalho!';
banner.classList.remove('hidden');
// Reinitialize icons in success banner
lucide.createIcons();
notify('success', { score: 100 });
notify('completed', { score: 100 });
}
// ═══════════════════════════════════════════════════════════════════════
// STEP 1: Move Mouse & Track Coverage
// ═══════════════════════════════════════════════════════════════════════
function setupMoveStep() {
coverWrap.classList.remove('hidden');
arena.style.cursor = 'crosshair';
const GRID_SIZE = 32;
const cols = Math.ceil(arena.clientWidth / GRID_SIZE);
const rows = Math.ceil(arena.clientHeight / GRID_SIZE);
const totalCells = cols * rows;
const visitedCells = new Set();
arena.addEventListener('mousemove', handleMouseMove);
function handleMouseMove(e) {
const rect = arena.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Track visited cells
const col = Math.floor(x / GRID_SIZE);
const row = Math.floor(y / GRID_SIZE);
const cellId = `${col},${row}`;
visitedCells.add(cellId);
const coverage = (visitedCells.size / totalCells) * 100;
coverBar.style.width = `${coverage}%`;
progLabel.textContent = `${Math.floor(coverage)}%`;
// Create cursor trail
const trail = document.createElement('div');
trail.className = 'absolute w-8 h-8 rounded-full bg-red-400/30 pointer-events-none';
trail.style.left = `${x}px`;
trail.style.top = `${y}px`;
trail.style.transform = 'translate(-50%, -50%)';
arena.appendChild(trail);
setTimeout(() => trail.remove(), 800);
// Success condition
if (coverage >= 60) {
arena.removeEventListener('mousemove', handleMouseMove);
nextStep('Ótimo! Você explorou a área com sucesso!');
}
}
}
// ═══════════════════════════════════════════════════════════════════════
// STEP 2: Single Click
// ═══════════════════════════════════════════════════════════════════════
function setupClickStep() {
arena.style.cursor = 'default';
placeTarget(false);
}
// ═══════════════════════════════════════════════════════════════════════
// STEP 3: Double Click
// ═══════════════════════════════════════════════════════════════════════
function setupDblClickStep() {
arena.style.cursor = 'default';
placeTarget(true);
}
function placeTarget(isDblClick) {
const SIZE = 140;
const maxX = arena.clientWidth - SIZE - 20;
const maxY = arena.clientHeight - SIZE - 20;
const x = Math.floor(Math.random() * maxX) + 10;
const y = Math.floor(Math.random() * maxY) + 10;
const button = document.createElement('button');
button.className = 'absolute bg-green-500 hover:bg-green-600 active:scale-95 text-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-200 flex items-center justify-center gap-3 text-2xl font-bold';
button.style.width = `${SIZE}px`;
button.style.height = `${SIZE}px`;
button.style.left = `${x}px`;
button.style.top = `${y}px`;
// Add Lucide icon
const icon = document.createElement('i');
icon.setAttribute('data-lucide', isDblClick ? 'pointer' : 'hand');
icon.className = 'w-16 h-16';
button.appendChild(icon);
arena.appendChild(button);
// Initialize the icon
lucide.createIcons();
if (isDblClick) {
button.addEventListener('dblclick', () => {
button.remove();
nextStep('Incrível! Você aprendeu o duplo clique!');
});
// Mobile fallback: two taps
let tapCount = 0;
let tapTimer;
button.addEventListener('click', (e) => {
tapCount++;
if (tapCount === 1) {
tapTimer = setTimeout(() => { tapCount = 0; }, 400);
} else if (tapCount === 2) {
clearTimeout(tapTimer);
button.remove();
nextStep('Incrível! Você aprendeu o duplo clique!');
}
e.stopPropagation();
});
} else {
button.addEventListener('click', () => {
button.remove();
nextStep('Perfeito! Você clicou no alvo!');
});
}
}
// ── Initialize ───────────────────────────────────────────────────────
renderStep(0);

View File

@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse - Básico</title>
<link rel="stylesheet" href="../../shared/letramento.css">
<script src="../../shared/lucide.js"></script>
<style>
body {
overflow: hidden;
height: 100vh;
width: 100vw;
}
</style>
</head>
<body class="bg-gradient-to-br from-brand-50 to-gray-100 flex items-center justify-center h-screen w-screen overflow-hidden p-4">
<div class="w-full h-full flex flex-col">
<!-- Activity Card -->
<div class="bg-white/95 backdrop-blur-sm border border-white/30 rounded-xl shadow-xl flex-1 flex flex-col overflow-hidden">
<!-- Instructions Header -->
<div class="flex items-center justify-center gap-4 p-4 border-b border-white/20">
<i data-lucide="mouse-pointer" class="w-12 h-12 text-red-500"></i>
<div>
<h2 class="text-2xl font-bold text-gray-800" id="instruction">Carregando...</h2>
<p class="text-lg text-gray-600" id="hint"></p>
</div>
</div>
<!-- Content Container -->
<div class="flex-1 p-6 overflow-hidden flex flex-col">
<!-- Progress Bar (Step 1 only) -->
<div id="coverageWrap" class="hidden mb-4">
<div class="flex items-center justify-between mb-2">
<p class="text-lg font-medium text-gray-700">Cobertura da área:</p>
<p class="text-lg font-bold text-red-600" id="progressLabel">0%</p>
</div>
<div class="w-full h-6 bg-gray-200 rounded-full overflow-hidden shadow-inner">
<div id="coverageBar" class="h-full bg-red-500 transition-all duration-300 rounded-full" style="width: 0%"></div>
</div>
</div>
<!-- Arena -->
<div id="arena" class="relative flex-1 bg-white/50 rounded-xl border-2 border-gray-200 shadow-inner overflow-hidden"></div>
<!-- Success Banner -->
<div id="successBanner" class="hidden mt-6 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-8 flex items-center justify-center gap-4">
<i data-lucide="check-circle" class="text-green-600 w-16 h-16"></i>
<div>
<h2 class="text-4xl font-bold text-green-700 mb-2">Muito bem!</h2>
<p class="text-xl text-gray-700" id="successMsg"></p>
</div>
</div>
</div>
</div>
</div>
<script src="./activity.js"></script>
<script>
// Initialize Lucide icons
lucide.createIcons();
</script>
</body>
</html>

View File

@@ -0,0 +1,73 @@
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
function notify(type, payload = {}) {
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
}
const arena = document.getElementById('arena');
const contextMenu = document.getElementById('contextMenu');
const banner = document.getElementById('successBanner');
let currentStep = 0;
notify('started');
// Create target icon
const target = document.createElement('div');
target.className = 'flex items-center gap-3 bg-white border-2 border-gray-300 rounded-lg p-6 shadow-lg';
target.innerHTML = `
<i data-lucide="file" class="w-16 h-16 text-red-500"></i>
<div>
<p class="text-2xl font-bold text-gray-800">Documento.txt</p>
<p class="text-sm text-gray-600">Clique com botão direito aqui</p>
</div>
`;
arena.appendChild(target);
lucide.createIcons();
target.addEventListener('contextmenu', (e) => {
e.preventDefault();
if (currentStep === 0) {
notify('running', { step: 1 });
currentStep = 1;
}
showContextMenu(e.pageX, e.pageY);
});
function showContextMenu(x, y) {
contextMenu.style.left = `${x - arena.getBoundingClientRect().left}px`;
contextMenu.style.top = `${y - arena.getBoundingClientRect().top}px`;
contextMenu.innerHTML = `
<button class="w-full text-left px-4 py-2 hover:bg-gray-100 rounded flex items-center gap-2" data-action="open">
<i data-lucide="folder-open" class="w-4 h-4"></i>
Abrir
</button>
<button class="w-full text-left px-4 py-2 hover:bg-gray-100 rounded flex items-center gap-2" data-action="copy">
<i data-lucide="copy" class="w-4 h-4"></i>
Copiar
</button>
<button class="w-full text-left px-4 py-2 hover:bg-gray-100 rounded flex items-center gap-2" data-action="delete">
<i data-lucide="trash-2" class="w-4 h-4"></i>
Excluir
</button>
`;
contextMenu.classList.remove('hidden');
lucide.createIcons();
contextMenu.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', () => {
contextMenu.classList.add('hidden');
target.remove();
arena.classList.add('hidden');
banner.classList.remove('hidden');
lucide.createIcons();
notify('success', { score: 100 });
notify('completed', { score: 100 });
});
});
}
document.addEventListener('click', () => {
contextMenu.classList.add('hidden');
});

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse - Botão Direito</title>
<link rel="stylesheet" href="../../shared/letramento.css">
<script src="../../shared/lucide.js"></script>
<style>
body { overflow: hidden; height: 100vh; width: 100vw; }
</style>
</head>
<body class="bg-gradient-to-br from-brand-50 to-gray-100 flex items-center justify-center h-screen w-screen overflow-hidden p-4">
<div class="w-full h-full flex flex-col">
<div class="bg-white/95 backdrop-blur-sm border border-white/30 rounded-xl shadow-xl flex-1 flex flex-col overflow-hidden">
<div class="flex items-center justify-center gap-4 p-4 border-b border-white/20">
<i data-lucide="mouse-pointer-click" class="w-12 h-12 text-red-600"></i>
<h2 class="text-2xl font-bold text-gray-800">Clique com botão direito</h2>
</div>
<div class="flex-1 p-6 overflow-hidden">
<div id="arena" class="relative h-full bg-white/50 rounded-xl border-2 border-gray-200 shadow-inner overflow-hidden flex items-center justify-center">
<div id="contextMenu" class="hidden absolute bg-white border-2 border-gray-300 rounded-lg shadow-2xl p-2 min-w-[200px]"></div>
</div>
<div id="successBanner" class="hidden mt-6 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-8 flex items-center justify-center gap-4">
<i data-lucide="check-circle" class="text-green-600 w-16 h-16"></i>
<div>
<h2 class="text-4xl font-bold text-green-700 mb-2">Muito bem!</h2>
<p class="text-xl text-gray-700">Você aprendeu o botão direito!</p>
</div>
</div>
</div>
</div>
</div>
<script src="./activity.js"></script>
<script>lucide.createIcons();</script>
</body>
</html>

View File

@@ -0,0 +1,64 @@
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
function notify(type, payload = {}) {
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
}
const arena = document.getElementById('arena');
const banner = document.getElementById('successBanner');
let remaining = 10;
notify('started');
function createTargets() {
for (let i = 0; i < 10; i++) {
const SIZE = 80;
const maxX = arena.clientWidth - SIZE - 20;
const maxY = arena.clientHeight - SIZE - 20;
let x, y, overlapping;
do {
overlapping = false;
x = Math.floor(Math.random() * maxX) + 10;
y = Math.floor(Math.random() * maxY) + 10;
document.querySelectorAll('.target').forEach(existing => {
const ex = parseInt(existing.style.left);
const ey = parseInt(existing.style.top);
const dist = Math.sqrt((x - ex) ** 2 + (y - ey) ** 2);
if (dist < SIZE + 20) overlapping = true;
});
} while (overlapping);
const target = document.createElement('button');
target.className = 'target absolute bg-gradient-to-br from-red-500 to-red-600 rounded-full shadow-lg hover:scale-110 transition-transform';
target.style.width = `${SIZE}px`;
target.style.height = `${SIZE}px`;
target.style.left = `${x}px`;
target.style.top = `${y}px`;
arena.appendChild(target);
target.addEventListener('click', () => {
target.classList.remove('from-red-500', 'to-red-600');
target.classList.add('from-green-400', 'to-green-500', 'opacity-50');
target.disabled = true;
remaining--;
notify('running', { remaining });
if (remaining === 0) {
setTimeout(() => {
arena.classList.add('hidden');
banner.classList.remove('hidden');
lucide.createIcons();
notify('success', { score: 100 });
notify('completed', { score: 100 });
}, 500);
}
});
}
}
createTargets();

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse - Múltiplos Cliques</title>
<link rel="stylesheet" href="../../shared/letramento.css">
<script src="../../shared/lucide.js"></script>
<style>
body { overflow: hidden; height: 100vh; width: 100vw; }
</style>
</head>
<body class="bg-gradient-to-br from-brand-50 to-gray-100 flex items-center justify-center h-screen w-screen overflow-hidden p-4">
<div class="w-full h-full flex flex-col">
<div class="bg-white/95 backdrop-blur-sm border border-white/30 rounded-xl shadow-xl flex-1 flex flex-col overflow-hidden">
<div class="flex items-center justify-center gap-4 p-4 border-b border-white/20">
<i data-lucide="grid-3x3" class="w-12 h-12 text-red-600"></i>
<h2 class="text-2xl font-bold text-gray-800">Clique em todos os círculos vermelhos</h2>
</div>
<div class="flex-1 p-6 overflow-hidden">
<div id="arena" class="relative h-full bg-white/50 rounded-xl border-2 border-gray-200 shadow-inner overflow-hidden"></div>
<div id="successBanner" class="hidden mt-6 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-8 flex items-center justify-center gap-4">
<i data-lucide="check-circle" class="text-green-600 w-16 h-16"></i>
<div>
<h2 class="text-4xl font-bold text-green-700 mb-2">Muito bem!</h2>
<p class="text-xl text-gray-700">Você clicou em todos!</p>
</div>
</div>
</div>
</div>
</div>
<script src="./activity.js"></script>
<script>lucide.createIcons();</script>
</body>
</html>

View File

@@ -0,0 +1,592 @@
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
function notify(type, payload = {}) {
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
}
const arena = document.getElementById('arena');
const instrEl = document.getElementById('instruction');
const banner = document.getElementById('successBanner');
let currentChallenge = 0;
const challenges = [
{ name: 'Fase 1: Cliques Precisos', task: precisionChallenge },
{ name: 'Fase 2: Sequência Rápida', task: sequenceSpeedChallenge },
{ name: 'Fase 3: Seguir Trilha', task: pathFollowChallenge },
{ name: 'Fase 4: Múltiplos Alvos', task: multiTargetChallenge },
{ name: 'Fase 5: Menu de Contexto', task: contextMenuChallenge },
{ name: 'Fase 6: Organizar Arquivos', task: fileOrganizerChallenge },
{ name: 'Fase 7: Desenhar Círculo', task: drawCircleChallenge },
{ name: 'Fase 8: Desafio Final', task: finalBossChallenge },
];
notify('started');
function nextChallenge() {
if (currentChallenge >= challenges.length) {
finishActivity();
return;
}
arena.innerHTML = '';
const challenge = challenges[currentChallenge];
instrEl.textContent = challenge.name;
notify('running', { step: currentChallenge + 1 });
challenge.task();
}
// Fase 1: Cliques em alvos progressivamente menores (precisão)
function precisionChallenge() {
let targets = 0;
const sizes = [80, 60, 45, 35];
function createTarget() {
if (targets >= sizes.length) {
currentChallenge++;
setTimeout(nextChallenge, 800);
return;
}
const target = document.createElement('button');
const size = sizes[targets];
const maxX = arena.clientWidth - size - 20;
const maxY = arena.clientHeight - size - 20;
target.className = 'absolute bg-gradient-to-br from-red-500 to-red-600 rounded-full shadow-xl hover:scale-110 transition-transform';
target.style.width = `${size}px`;
target.style.height = `${size}px`;
target.style.left = `${20 + Math.random() * maxX}px`;
target.style.top = `${20 + Math.random() * maxY}px`;
target.innerHTML = `<span class="text-white text-xl font-bold">${targets + 1}</span>`;
target.addEventListener('click', () => {
target.remove();
targets++;
createTarget();
});
arena.appendChild(target);
}
createTarget();
}
// Fase 2: Clicar números 1-5 o mais rápido possível
function sequenceSpeedChallenge() {
const startTime = Date.now();
let current = 1;
const total = 5;
for (let i = 1; i <= total; i++) {
const btn = document.createElement('button');
btn.className = 'absolute w-20 h-20 bg-red-500 text-white text-2xl font-bold rounded-full shadow-lg hover:scale-110 transition-transform';
btn.textContent = i;
btn.style.left = `${Math.random() * (arena.clientWidth - 100)}px`;
btn.style.top = `${Math.random() * (arena.clientHeight - 100)}px`;
btn.addEventListener('click', () => {
if (i === current) {
btn.classList.remove('bg-red-500', 'to-red-600');
btn.classList.add('bg-green-400', 'to-green-500', 'opacity-50');
// btn.style.background = '#10b981';
btn.disabled = true;
current++;
if (current > total) {
const time = ((Date.now() - startTime) / 1000).toFixed(1);
setTimeout(() => {
instrEl.textContent = `Fase 2: Concluída em ${time}s!`;
currentChallenge++;
setTimeout(nextChallenge, 1500);
}, 300);
}
} else {
btn.classList.add('animate-bounce');
setTimeout(() => btn.classList.remove('animate-bounce'), 500);
}
});
arena.appendChild(btn);
}
}
// Fase 3: Seguir caminho sem sair (CÓPIA EXATA da atividade mouse-controle)
function pathFollowChallenge() {
const width = arena.clientWidth;
const height = arena.clientHeight;
// Criar SVG
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', width);
svg.setAttribute('height', height);
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
svg.className = 'absolute inset-0';
arena.appendChild(svg);
let started = false;
let progress = 0;
let completed = false; // Flag para evitar múltiplas conclusões
const TARGET_PROGRESS = 90;
// Create curved path
const pathData = `M 50,${height/2}
Q ${width/4},${height/4} ${width/2},${height/2}
T ${width-50},${height/2}`;
// Background path (wider, gray)
const bgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
bgPath.setAttribute('d', pathData);
bgPath.setAttribute('stroke', '#e5e7eb');
bgPath.setAttribute('stroke-width', '120');
bgPath.setAttribute('fill', 'none');
bgPath.setAttribute('stroke-linecap', 'round');
svg.appendChild(bgPath);
// Progress path (green)
const progressPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
progressPath.setAttribute('d', pathData);
progressPath.setAttribute('stroke', '#22c55e');
progressPath.setAttribute('stroke-width', '100');
progressPath.setAttribute('fill', 'none');
progressPath.setAttribute('stroke-linecap', 'round');
progressPath.setAttribute('stroke-dasharray', '1000');
progressPath.setAttribute('stroke-dashoffset', '1000');
svg.appendChild(progressPath);
// Start point with pulse animation
const startCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
startCircle.setAttribute('cx', '50');
startCircle.setAttribute('cy', height/2);
startCircle.setAttribute('r', '30');
startCircle.setAttribute('fill', '#ef4444');
startCircle.setAttribute('cursor', 'pointer');
startCircle.style.animation = 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite';
svg.appendChild(startCircle);
// Pulse ring for extra visibility
const pulseRing = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
pulseRing.setAttribute('cx', '50');
pulseRing.setAttribute('cy', height/2);
pulseRing.setAttribute('r', '30');
pulseRing.setAttribute('fill', 'none');
pulseRing.setAttribute('stroke', '#ef4444');
pulseRing.setAttribute('stroke-width', '3');
svg.appendChild(pulseRing);
// Animate pulse ring
pulseRing.innerHTML = '<animate attributeName="r" from="30" to="50" dur="1.5s" repeatCount="indefinite" /><animate attributeName="opacity" from="0.8" to="0" dur="1.5s" repeatCount="indefinite" />';
// Add "Click here" text
const startText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
startText.setAttribute('x', '50');
startText.setAttribute('y', height/2 + 60);
startText.setAttribute('text-anchor', 'middle');
startText.setAttribute('fill', '#ef4444');
startText.setAttribute('font-size', '16');
startText.setAttribute('font-weight', 'bold');
startText.textContent = 'Clique aqui';
svg.appendChild(startText);
// Add click to start
startCircle.addEventListener('click', () => {
if (!started) {
started = true;
startCircle.setAttribute('fill', '#22c55e');
startCircle.style.animation = '';
pulseRing.remove();
startText.remove();
}
});
// End point
const endCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
endCircle.setAttribute('cx', width - 50);
endCircle.setAttribute('cy', height/2);
endCircle.setAttribute('r', '30');
endCircle.setAttribute('fill', '#22c55e');
svg.appendChild(endCircle);
// Track mouse movement
svg.addEventListener('mousemove', (e) => {
if (!started) return;
const rect = svg.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Check if on path
const pathLength = bgPath.getTotalLength();
let minDist = Infinity;
for (let i = 0; i < pathLength; i += 10) {
const point = bgPath.getPointAtLength(i);
const dist = Math.sqrt((x - point.x) ** 2 + (y - point.y) ** 2);
if (dist < minDist) minDist = dist;
}
if (minDist < 60) {
// On path
const progressPercent = (x / width) * 100;
if (progressPercent > progress) {
progress = progressPercent;
const offset = 1000 - (progress / 100) * 1000;
progressPath.setAttribute('stroke-dashoffset', offset);
if (progress >= TARGET_PROGRESS && !completed) {
// Avança para a próxima fase do desafio completo
completed = true;
currentChallenge++;
setTimeout(nextChallenge, 800);
}
}
}
});
}
// Fase 4: Clicar 8 alvos simultâneos
function multiTargetChallenge() {
let remaining = 8;
for (let i = 0; i < remaining; i++) {
const target = document.createElement('button');
target.className = 'absolute w-16 h-16 bg-gradient-to-br from-red-500 to-red-600 rounded-full shadow-lg hover:scale-110 transition-transform';
target.style.left = `${50 + Math.random() * (arena.clientWidth - 150)}px`;
target.style.top = `${50 + Math.random() * (arena.clientHeight - 150)}px`;
const bullseye = document.createElement('div');
bullseye.className = 'absolute inset-0 flex items-center justify-center';
bullseye.innerHTML = '<div class="w-6 h-6 bg-white rounded-full"></div>';
target.appendChild(bullseye);
target.addEventListener('click', () => {
target.remove();
remaining--;
if (remaining === 0) {
currentChallenge++;
setTimeout(nextChallenge, 800);
}
});
arena.appendChild(target);
}
lucide.createIcons();
}
// Fase 5: Menu de contexto e seleção correta
function contextMenuChallenge() {
const file = document.createElement('div');
file.className = 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-32 h-32 bg-white border-2 border-gray-300 rounded-lg flex flex-col items-center justify-center gap-2 cursor-pointer';
file.innerHTML = '<i data-lucide="file-text" class="w-16 h-16 text-blue-500"></i><span class="text-sm font-semibold">arquivo.txt</span>';
arena.appendChild(file);
const menu = document.createElement('div');
menu.className = 'absolute hidden bg-white rounded-lg shadow-2xl border border-gray-200 py-2 min-w-[180px]';
menu.innerHTML = `
<button class="w-full px-4 py-2 text-left hover:bg-gray-100 flex items-center gap-2" data-action="open">
<i data-lucide="folder-open" class="w-4 h-4"></i>Abrir
</button>
<button class="w-full px-4 py-2 text-left hover:bg-gray-100 flex items-center gap-2" data-action="copy">
<i data-lucide="copy" class="w-4 h-4"></i>Copiar
</button>
<button class="w-full px-4 py-2 text-left hover:bg-gray-100 flex items-center gap-2" data-action="delete">
<i data-lucide="trash-2" class="w-4 h-4 text-red-500"></i>Excluir
</button>
<button class="w-full px-4 py-2 text-left hover:bg-green-100 text-green-700 font-semibold flex items-center gap-2" data-action="properties">
<i data-lucide="info" class="w-4 h-4"></i>Propriedades ✓
</button>
`;
arena.appendChild(menu);
lucide.createIcons();
file.addEventListener('contextmenu', (e) => {
e.preventDefault();
menu.classList.remove('hidden');
menu.style.left = `${e.clientX - arena.getBoundingClientRect().left}px`;
menu.style.top = `${e.clientY - arena.getBoundingClientRect().top}px`;
});
menu.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', () => {
if (btn.dataset.action === 'properties') {
currentChallenge++;
setTimeout(nextChallenge, 800);
} else {
menu.classList.add('hidden');
btn.classList.add('bg-red-100');
setTimeout(() => {
btn.classList.remove('bg-red-100');
menu.classList.add('hidden');
}, 500);
}
});
});
arena.addEventListener('click', () => {
menu.classList.add('hidden');
});
}
// Fase 6: Arrastar 5 itens para pastas corretas
function fileOrganizerChallenge() {
const files = [
{ name: 'foto.jpg', type: 'image', icon: 'image', folder: 'images' },
{ name: 'musica.mp3', type: 'music', icon: 'music', folder: 'music' },
{ name: 'video.mp4', type: 'video', icon: 'video', folder: 'videos' },
{ name: 'doc.pdf', type: 'document', icon: 'file-text', folder: 'documents' },
{ name: 'code.js', type: 'code', icon: 'code', folder: 'code' },
];
const folders = [
{ id: 'images', label: 'Imagens', color: 'blue' },
{ id: 'music', label: 'Músicas', color: 'purple' },
{ id: 'videos', label: 'Vídeos', color: 'red' },
{ id: 'documents', label: 'Documentos', color: 'yellow' },
{ id: 'code', label: 'Código', color: 'green' },
];
let completed = 0;
// Criar pastas
folders.forEach((folder, i) => {
const div = document.createElement('div');
div.className = `absolute w-28 h-28 border-4 border-dashed border-${folder.color}-400 bg-${folder.color}-50 rounded-xl flex flex-col items-center justify-center gap-1`;
div.style.left = `${50 + i * 140}px`;
div.style.bottom = '30px';
div.innerHTML = `<i data-lucide="folder" class="w-8 h-8 text-${folder.color}-600"></i><span class="text-xs font-semibold text-${folder.color}-700">${folder.label}</span>`;
div.dataset.folderId = folder.id;
arena.appendChild(div);
div.addEventListener('dragover', (e) => {
e.preventDefault();
div.classList.add('scale-110', 'shadow-xl');
});
div.addEventListener('dragleave', () => {
div.classList.remove('scale-110', 'shadow-xl');
});
div.addEventListener('drop', (e) => {
e.preventDefault();
div.classList.remove('scale-110', 'shadow-xl');
const fileId = e.dataTransfer.getData('text/plain');
const file = files.find(f => f.name === fileId);
if (file && file.folder === folder.id) {
const dragged = document.querySelector(`[data-file="${fileId}"]`);
if (dragged) {
dragged.remove();
completed++;
if (completed === files.length) {
currentChallenge++;
setTimeout(nextChallenge, 800);
}
}
} else {
div.classList.add('animate-bounce');
setTimeout(() => div.classList.remove('animate-bounce'), 500);
}
});
});
// Criar arquivos
files.forEach((file, i) => {
const div = document.createElement('div');
div.className = 'absolute w-20 h-20 bg-white border-2 border-gray-300 rounded-lg cursor-grab flex flex-col items-center justify-center gap-1 hover:shadow-lg transition-shadow';
div.style.left = `${100 + i * 100}px`;
div.style.top = '80px';
div.innerHTML = `<i data-lucide="${file.icon}" class="w-8 h-8 text-gray-700"></i><span class="text-xs font-medium truncate">${file.name}</span>`;
div.draggable = true;
div.dataset.file = file.name;
div.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', file.name);
div.classList.add('opacity-50');
});
div.addEventListener('dragend', () => {
div.classList.remove('opacity-50');
});
arena.appendChild(div);
});
lucide.createIcons();
}
// Fase 7: Desenhar círculo com 60% de cobertura
function drawCircleChallenge() {
const canvas = document.createElement('canvas');
canvas.width = arena.clientWidth;
canvas.height = arena.clientHeight;
canvas.className = 'absolute inset-0';
arena.appendChild(canvas);
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 120;
// Desenha círculo guia pontilhado
ctx.setLineDash([10, 10]);
ctx.strokeStyle = '#9ca3af';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
ctx.stroke();
ctx.setLineDash([]);
// Texto instrução
ctx.fillStyle = '#6b7280';
ctx.font = 'bold 16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Desenhe sobre o círculo (60% mínimo)', centerX, centerY - radius - 30);
let isDrawing = false;
const segments = new Set();
const totalSegments = 120;
canvas.addEventListener('mousedown', () => {
isDrawing = true;
});
canvas.addEventListener('mousemove', (e) => {
if (!isDrawing) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const dist = Math.hypot(x - centerX, y - centerY);
if (Math.abs(dist - radius) < 25) {
const angle = Math.atan2(y - centerY, x - centerX);
const segmentId = Math.floor(((angle + Math.PI) / (2 * Math.PI)) * totalSegments);
segments.add(segmentId);
// Desenha ponto
ctx.fillStyle = '#ef4444';
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fill();
const coverage = (segments.size / totalSegments) * 100;
if (coverage >= 60) {
isDrawing = false;
setTimeout(() => {
currentChallenge++;
nextChallenge();
}, 500);
}
}
});
canvas.addEventListener('mouseup', () => {
isDrawing = false;
});
}
// Fase 8: Desafio final - alvos móveis com tempo limitado
function finalBossChallenge() {
let score = 0;
let timeLeft = 20;
const targetCount = 25;
const timer = document.createElement('div');
timer.className = 'absolute top-4 left-1/2 -translate-x-1/2 text-4xl font-bold text-red-600 bg-white px-6 py-2 rounded-full shadow-xl';
arena.appendChild(timer);
const scoreDisplay = document.createElement('div');
scoreDisplay.className = 'absolute top-4 right-4 text-2xl font-bold text-green-600 bg-white px-4 py-2 rounded-full shadow-xl';
arena.appendChild(scoreDisplay);
function updateDisplay() {
timer.textContent = `${timeLeft}s`;
scoreDisplay.textContent = `${score}/${targetCount}`;
}
function createMovingTarget() {
const target = document.createElement('button');
const size = 40 + Math.random() * 30;
target.className = 'absolute bg-gradient-to-br from-red-500 to-orange-500 rounded-full shadow-xl hover:scale-125 transition-transform';
target.style.width = `${size}px`;
target.style.height = `${size}px`;
let x = Math.random() * (arena.clientWidth - size);
let y = Math.random() * (arena.clientHeight - size);
let vx = (Math.random() - 0.5) * 3;
let vy = (Math.random() - 0.5) * 3;
target.style.left = `${x}px`;
target.style.top = `${y}px`;
const moveInterval = setInterval(() => {
x += vx;
y += vy;
if (x < 0 || x > arena.clientWidth - size) vx *= -1;
if (y < 0 || y > arena.clientHeight - size) vy *= -1;
target.style.left = `${x}px`;
target.style.top = `${y}px`;
}, 20);
target.addEventListener('click', () => {
clearInterval(moveInterval);
target.remove();
score++;
updateDisplay();
if (score >= targetCount) {
clearInterval(gameInterval);
setTimeout(() => {
currentChallenge++;
nextChallenge();
}, 500);
} else {
createMovingTarget();
}
});
arena.appendChild(target);
}
// Criar alvos iniciais
for (let i = 0; i < 5; i++) {
createMovingTarget();
}
updateDisplay();
const gameInterval = setInterval(() => {
timeLeft--;
updateDisplay();
if (timeLeft <= 0) {
clearInterval(gameInterval);
if (score >= targetCount * 0.7) {
alert(`Bom trabalho! ${score}/${targetCount} alvos`);
currentChallenge++;
nextChallenge();
} else {
alert(`Tempo esgotado! Você acertou ${score}/${targetCount}. Tente novamente!`);
finalBossChallenge();
}
}
}, 1000);
}
function finishActivity() {
arena.classList.add('hidden');
banner.classList.remove('hidden');
lucide.createIcons();
notify('success', { score: 100 });
notify('completed', { score: 100 });
}
nextChallenge();

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse - Desafio Completo</title>
<link rel="stylesheet" href="../../shared/letramento.css">
<script src="../../shared/lucide.js"></script>
<style>
body { overflow: hidden; height: 100vh; width: 100vw; }
.dragging { opacity: 0.5; }
</style>
</head>
<body class="bg-gradient-to-br from-brand-50 to-gray-100 flex items-center justify-center h-screen w-screen overflow-hidden p-4">
<div class="w-full h-full flex flex-col">
<div class="bg-white/95 backdrop-blur-sm border border-white/30 rounded-xl shadow-xl flex-1 flex flex-col overflow-hidden">
<div class="flex items-center justify-center gap-4 p-4 border-b border-white/20">
<i data-lucide="trophy" class="w-12 h-12 text-red-600"></i>
<h2 id="instruction" class="text-2xl font-bold text-gray-800">Desafio Completo</h2>
</div>
<div class="flex-1 p-6 overflow-hidden">
<div id="arena" class="relative h-full bg-white/50 rounded-xl border-2 border-gray-200 shadow-inner overflow-hidden"></div>
<div id="successBanner" class="hidden mt-6 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-8 flex items-center justify-center gap-4">
<i data-lucide="check-circle" class="text-green-600 w-16 h-16"></i>
<div>
<h2 class="text-4xl font-bold text-green-700 mb-2">Parabéns!</h2>
<p class="text-xl text-gray-700">Você dominou todas as habilidades do mouse!</p>
</div>
</div>
</div>
</div>
</div>
<script src="./activity.js"></script>
<script>lucide.createIcons();</script>
</body>
</html>

View File

@@ -0,0 +1,144 @@
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
function notify(type, payload = {}) {
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
}
const arena = document.getElementById('arena');
const svg = document.getElementById('pathSvg');
const banner = document.getElementById('successBanner');
const width = arena.clientWidth;
const height = arena.clientHeight;
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
let started = false;
let onPath = true;
let progress = 0;
const TARGET_PROGRESS = 90;
notify('started');
// Create curved path
const pathData = `M 50,${height/2}
Q ${width/4},${height/4} ${width/2},${height/2}
T ${width-50},${height/2}`;
// Background path (wider, gray)
const bgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
bgPath.setAttribute('d', pathData);
bgPath.setAttribute('stroke', '#e5e7eb');
bgPath.setAttribute('stroke-width', '120');
bgPath.setAttribute('fill', 'none');
bgPath.setAttribute('stroke-linecap', 'round');
svg.appendChild(bgPath);
// Progress path (green)
const progressPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
progressPath.setAttribute('d', pathData);
progressPath.setAttribute('stroke', '#22c55e');
progressPath.setAttribute('stroke-width', '100');
progressPath.setAttribute('fill', 'none');
progressPath.setAttribute('stroke-linecap', 'round');
progressPath.setAttribute('stroke-dasharray', '1000');
progressPath.setAttribute('stroke-dashoffset', '1000');
svg.appendChild(progressPath);
// Start point with pulse animation
const startCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
startCircle.setAttribute('cx', '50');
startCircle.setAttribute('cy', height/2);
startCircle.setAttribute('r', '30');
startCircle.setAttribute('fill', '#ef4444');
startCircle.setAttribute('cursor', 'pointer');
startCircle.classList.add('pulse-circle');
svg.appendChild(startCircle);
// Pulse ring for extra visibility
const pulseRing = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
pulseRing.setAttribute('cx', '50');
pulseRing.setAttribute('cy', height/2);
pulseRing.setAttribute('r', '30');
pulseRing.setAttribute('fill', 'none');
pulseRing.setAttribute('stroke', '#ef4444');
pulseRing.setAttribute('stroke-width', '3');
svg.appendChild(pulseRing);
// Animate pulse ring
const animatePulseRing = () => {
pulseRing.innerHTML = '<animate attributeName="r" from="30" to="50" dur="1.5s" repeatCount="indefinite" /><animate attributeName="opacity" from="0.8" to="0" dur="1.5s" repeatCount="indefinite" />';
};
animatePulseRing();
// Add "Click here" text
const startText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
startText.setAttribute('x', '50');
startText.setAttribute('y', height/2 + 60);
startText.setAttribute('text-anchor', 'middle');
startText.setAttribute('fill', '#ef4444');
startText.setAttribute('font-size', '16');
startText.setAttribute('font-weight', 'bold');
startText.textContent = 'Clique aqui';
svg.appendChild(startText);
// Add click to start
startCircle.addEventListener('click', () => {
if (!started) {
started = true;
startCircle.setAttribute('fill', '#22c55e');
startCircle.classList.remove('pulse-circle');
pulseRing.remove(); // Remove o anel pulsante
startText.remove(); // Remove o texto
notify('running', { progress: 0 });
}
});
// End point
const endCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
endCircle.setAttribute('cx', width - 50);
endCircle.setAttribute('cy', height/2);
endCircle.setAttribute('r', '30');
endCircle.setAttribute('fill', '#22c55e');
svg.appendChild(endCircle);
// Track mouse movement
svg.addEventListener('mousemove', (e) => {
if (!started) return; // Só processa se a atividade foi iniciada
const rect = svg.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Check if on path (simplified: check distance from path)
const pathLength = bgPath.getTotalLength();
let minDist = Infinity;
for (let i = 0; i < pathLength; i += 10) {
const point = bgPath.getPointAtLength(i);
const dist = Math.sqrt((x - point.x) ** 2 + (y - point.y) ** 2);
if (dist < minDist) minDist = dist;
}
if (minDist < 60) {
// On path
const progressPercent = (x / width) * 100;
if (progressPercent > progress) {
progress = progressPercent;
const offset = 1000 - (progress / 100) * 1000;
progressPath.setAttribute('stroke-dashoffset', offset);
notify('running', { progress: Math.floor(progress) });
if (progress >= TARGET_PROGRESS) {
finishActivity();
}
}
}
});
function finishActivity() {
arena.classList.add('hidden');
banner.classList.remove('hidden');
lucide.createIcons();
notify('success', { score: 100 });
notify('completed', { score: 100 });
}

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse - Controle</title>
<link rel="stylesheet" href="../../shared/letramento.css">
<script src="../../shared/lucide.js"></script>
<style>
body { overflow: hidden; height: 100vh; width: 100vw; }
#path { cursor: pointer; }
@keyframes pulse {
0%, 100% {
r: 30;
opacity: 1;
}
50% {
r: 35;
opacity: 0.8;
}
}
.pulse-circle {
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse-ring {
0% {
r: 30;
opacity: 0.8;
}
100% {
r: 50;
opacity: 0;
}
}
</style>
</head>
<body class="bg-gradient-to-br from-brand-50 to-gray-100 flex items-center justify-center h-screen w-screen overflow-hidden p-4">
<div class="w-full h-full flex flex-col">
<div class="bg-white/95 backdrop-blur-sm border border-white/30 rounded-xl shadow-xl flex-1 flex flex-col overflow-hidden">
<div class="flex items-center justify-center gap-4 p-4 border-b border-white/20">
<i data-lucide="navigation" class="w-12 h-12 text-red-600"></i>
<h2 class="text-2xl font-bold text-gray-800">Clique no círculo vermelho e siga o caminho</h2>
</div>
<div class="flex-1 p-6 overflow-hidden">
<div id="arena" class="relative h-full bg-white/50 rounded-xl border-2 border-gray-200 shadow-inner overflow-hidden flex items-center justify-center">
<svg id="pathSvg" class="w-full h-full"></svg>
</div>
<div id="successBanner" class="hidden mt-6 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-8 flex items-center justify-center gap-4">
<i data-lucide="check-circle" class="text-green-600 w-16 h-16"></i>
<div>
<h2 class="text-4xl font-bold text-green-700 mb-2">Muito bem!</h2>
<p class="text-xl text-gray-700">Você tem ótimo controle do mouse!</p>
</div>
</div>
</div>
</div>
</div>
<script src="./activity.js"></script>
<script>lucide.createIcons();</script>
</body>
</html>

View File

@@ -0,0 +1,97 @@
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
function notify(type, payload = {}) {
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const banner = document.getElementById('successBanner');
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
let drawing = false;
let coverage = 0;
const TARGET_COVERAGE = 60;
const circleSegments = new Set(); // Track which parts of the circle were drawn
notify('started');
// Draw guide path (dotted circle)
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(canvas.width, canvas.height) * 0.3;
ctx.setLineDash([10, 10]);
ctx.strokeStyle = '#ef4444';
ctx.lineWidth = 6;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.setLineDash([]);
// User drawing
let points = [];
canvas.addEventListener('mousedown', (e) => {
drawing = true;
const rect = canvas.getBoundingClientRect();
points = [{ x: e.clientX - rect.left, y: e.clientY - rect.top }];
});
canvas.addEventListener('mousemove', (e) => {
if (!drawing) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
points.push({ x, y });
// Draw line
ctx.strokeStyle = '#22c55e';
ctx.lineWidth = 8;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
ctx.moveTo(points[points.length - 2].x, points[points.length - 2].y);
ctx.lineTo(x, y);
ctx.stroke();
// Check if close to guide circle and mark segment as covered
const dist = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
if (Math.abs(dist - radius) < 30) {
// Calculate angle (0-360) to determine which segment of the circle
const angle = Math.atan2(y - centerY, x - centerX);
const degrees = ((angle * 180 / Math.PI) + 360) % 360;
const segment = Math.floor(degrees / 3); // Divide circle in 120 segments (360/3)
if (!circleSegments.has(segment)) {
circleSegments.add(segment);
coverage = (circleSegments.size / 120) * 100;
notify('running', { coverage: Math.floor(coverage) });
if (coverage >= TARGET_COVERAGE) {
finishActivity();
}
}
}
});
canvas.addEventListener('mouseup', () => {
drawing = false;
});
canvas.addEventListener('mouseleave', () => {
drawing = false;
});
function finishActivity() {
drawing = false;
canvas.classList.add('hidden');
banner.classList.remove('hidden');
lucide.createIcons();
notify('success', { score: 100 });
notify('completed', { score: 100 });
}

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse - Desenhar</title>
<link rel="stylesheet" href="../../shared/letramento.css">
<script src="../../shared/lucide.js"></script>
<style>
body { overflow: hidden; height: 100vh; width: 100vw; }
#canvas { cursor: crosshair; }
</style>
</head>
<body class="bg-gradient-to-br from-brand-50 to-gray-100 flex items-center justify-center h-screen w-screen overflow-hidden p-4">
<div class="w-full h-full flex flex-col">
<div class="bg-white/95 backdrop-blur-sm border border-white/30 rounded-xl shadow-xl flex-1 flex flex-col overflow-hidden">
<div class="flex items-center justify-center gap-4 p-4 border-b border-white/20">
<i data-lucide="pencil" class="w-12 h-12 text-red-600"></i>
<h2 class="text-2xl font-bold text-gray-800">Desenhe seguindo a linha pontilhada</h2>
</div>
<div class="flex-1 p-6 overflow-hidden">
<canvas id="canvas" class="w-full h-full bg-white/50 rounded-xl border-2 border-gray-200 shadow-inner"></canvas>
<div id="successBanner" class="hidden mt-6 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-8 flex items-center justify-center gap-4">
<i data-lucide="check-circle" class="text-green-600 w-16 h-16"></i>
<div>
<h2 class="text-4xl font-bold text-green-700 mb-2">Muito bem!</h2>
<p class="text-xl text-gray-700">Você completou o traçado!</p>
</div>
</div>
</div>
</div>
</div>
<script src="./activity.js"></script>
<script>lucide.createIcons();</script>
</body>
</html>

View File

@@ -0,0 +1,53 @@
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
function notify(type, payload = {}) {
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
}
const arena = document.getElementById('arena');
const banner = document.getElementById('successBanner');
let completed = 0;
const TOTAL_TARGETS = 5;
notify('started');
function placeTarget() {
const SIZE = 80;
const maxX = arena.clientWidth - SIZE - 20;
const maxY = arena.clientHeight - SIZE - 20;
const x = Math.floor(Math.random() * maxX) + 10;
const y = Math.floor(Math.random() * maxY) + 10;
const target = document.createElement('button');
target.className = 'absolute w-16 h-16 bg-gradient-to-br from-red-500 to-red-600 rounded-full shadow-lg hover:scale-110 transition-transform';
target.style.width = `${SIZE}px`;
target.style.height = `${SIZE}px`;
target.style.left = `${x}px`;
target.style.top = `${y}px`;
const bullseye = document.createElement('div');
bullseye.className = 'absolute inset-0 flex items-center justify-center';
bullseye.innerHTML = '<div class="w-6 h-6 bg-white rounded-full"></div>';
target.appendChild(bullseye);
arena.appendChild(target);
target.addEventListener('click', () => {
target.remove();
completed++;
notify('running', { step: completed });
if (completed < TOTAL_TARGETS) {
setTimeout(() => placeTarget(), 300);
} else {
arena.classList.add('hidden');
banner.classList.remove('hidden');
lucide.createIcons();
notify('success', { score: 100 });
notify('completed', { score: 100 });
}
});
}
placeTarget();

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse - Precisão</title>
<link rel="stylesheet" href="../../shared/letramento.css">
<script src="../../shared/lucide.js"></script>
<style>
body { overflow: hidden; height: 100vh; width: 100vw; }
@keyframes shrink { 0% { transform: scale(1); } 100% { transform: scale(0.5); } }
.shrinking { animation: shrink 5s linear infinite; }
</style>
</head>
<body class="bg-gradient-to-br from-brand-50 to-gray-100 flex items-center justify-center h-screen w-screen overflow-hidden p-4">
<div class="w-full h-full flex flex-col">
<div class="bg-white/95 backdrop-blur-sm border border-white/30 rounded-xl shadow-xl flex-1 flex flex-col overflow-hidden">
<div class="flex items-center justify-center gap-4 p-4 border-b border-white/20">
<i data-lucide="target" class="w-12 h-12 text-red-600"></i>
<h2 class="text-2xl font-bold text-gray-800">Clique no centro dos alvos</h2>
</div>
<div class="flex-1 p-6 overflow-hidden">
<div id="arena" class="relative h-full bg-white/50 rounded-xl border-2 border-gray-200 shadow-inner overflow-hidden"></div>
<div id="successBanner" class="hidden mt-6 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-8 flex items-center justify-center gap-4">
<i data-lucide="check-circle" class="text-green-600 w-16 h-16"></i>
<div>
<h2 class="text-4xl font-bold text-green-700 mb-2">Muito bem!</h2>
<p class="text-xl text-gray-700">Sua precisão está excelente!</p>
</div>
</div>
</div>
</div>
</div>
<script src="./activity.js"></script>
<script>lucide.createIcons();</script>
</body>
</html>

View File

@@ -0,0 +1,70 @@
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
function notify(type, payload = {}) {
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
}
const arena = document.getElementById('arena');
const banner = document.getElementById('successBanner');
let currentTarget = 1;
const TOTAL_TARGETS = 5;
const targets = [];
notify('started');
function createTargets() {
for (let i = 1; i <= TOTAL_TARGETS; i++) {
const SIZE = 120;
const maxX = arena.clientWidth - SIZE - 20;
const maxY = arena.clientHeight - SIZE - 20;
const x = Math.floor(Math.random() * maxX) + 10;
const y = Math.floor(Math.random() * maxY) + 10;
const button = document.createElement('button');
button.className = 'absolute bg-gradient-to-br from-gray-400 to-gray-500 rounded-full shadow-lg transition-all text-white font-bold text-4xl';
button.style.width = `${SIZE}px`;
button.style.height = `${SIZE}px`;
button.style.left = `${x}px`;
button.style.top = `${y}px`;
button.textContent = i;
button.dataset.number = i;
arena.appendChild(button);
targets.push(button);
button.addEventListener('click', () => handleClick(parseInt(button.dataset.number)));
}
updateActiveTarget();
}
function updateActiveTarget() {
targets.forEach((btn) => {
const num = parseInt(btn.dataset.number);
if (num === currentTarget) {
btn.className = 'absolute bg-gradient-to-br from-red-500 to-red-600 rounded-full shadow-2xl text-white font-bold text-4xl pulse-active';
} else if (num < currentTarget) {
btn.className = 'absolute bg-gradient-to-br from-green-400 to-green-500 rounded-full shadow-lg text-white font-bold text-4xl opacity-50';
}
});
}
function handleClick(num) {
if (num === currentTarget) {
notify('running', { step: currentTarget });
currentTarget++;
if (currentTarget > TOTAL_TARGETS) {
arena.classList.add('hidden');
banner.classList.remove('hidden');
lucide.createIcons();
notify('success', { score: 100 });
notify('completed', { score: 100 });
} else {
updateActiveTarget();
}
}
}
createTargets();

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse - Sequência</title>
<link rel="stylesheet" href="../../shared/letramento.css">
<script src="../../shared/lucide.js"></script>
<style>
body { overflow: hidden; height: 100vh; width: 100vw; }
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } }
.pulse-active { animation: pulse 1s ease-in-out infinite; }
</style>
</head>
<body class="bg-gradient-to-br from-brand-50 to-gray-100 flex items-center justify-center h-screen w-screen overflow-hidden p-4">
<div class="w-full h-full flex flex-col">
<div class="bg-white/95 backdrop-blur-sm border border-white/30 rounded-xl shadow-xl flex-1 flex flex-col overflow-hidden">
<div class="flex items-center justify-center gap-4 p-4 border-b border-white/20">
<i data-lucide="list-ordered" class="w-12 h-12 text-red-600"></i>
<h2 class="text-2xl font-bold text-gray-800">Clique na ordem: 1 → 2 → 3 → 4 → 5</h2>
</div>
<div class="flex-1 p-6 overflow-hidden">
<div id="arena" class="relative h-full bg-white/50 rounded-xl border-2 border-gray-200 shadow-inner overflow-hidden"></div>
<div id="successBanner" class="hidden mt-6 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-8 flex items-center justify-center gap-4">
<i data-lucide="check-circle" class="text-green-600 w-16 h-16"></i>
<div>
<h2 class="text-4xl font-bold text-green-700 mb-2">Muito bem!</h2>
<p class="text-xl text-gray-700">Você acertou a sequência!</p>
</div>
</div>
</div>
</div>
</div>
<script src="./activity.js"></script>
<script>lucide.createIcons();</script>
</body>
</html>

View File

@@ -0,0 +1,80 @@
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
function notify(type, payload = {}) {
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
}
const arena = document.getElementById('arena');
const banner = document.getElementById('successBanner');
const finalScoreEl = document.getElementById('finalScore');
let score = 0;
let timeLeft = 30;
let gameActive = true;
// Criar timer na cena
const timerEl = document.createElement('div');
timerEl.className = 'absolute top-4 left-1/2 -translate-x-1/2 text-4xl font-bold text-red-600 bg-white px-6 py-2 rounded-full shadow-xl';
timerEl.textContent = `${timeLeft}s`;
arena.appendChild(timerEl);
// Criar contador de score na cena
const scoreEl = document.createElement('div');
scoreEl.className = 'absolute top-4 right-4 text-2xl font-bold text-green-600 bg-white px-4 py-2 rounded-full shadow-xl';
scoreEl.textContent = `0 alvos`;
arena.appendChild(scoreEl);
notify('started');
const spawnInterval = setInterval(() => {
if (gameActive) spawnTarget();
}, 800);
const countdown = setInterval(() => {
timeLeft--;
timerEl.textContent = `${timeLeft}s`;
if (timeLeft <= 0) {
endGame();
}
}, 1000);
function spawnTarget() {
const SIZE = 100;
const maxX = arena.clientWidth - SIZE - 20;
const maxY = arena.clientHeight - SIZE - 20;
const x = Math.floor(Math.random() * maxX) + 10;
const y = Math.floor(Math.random() * maxY) + 10;
const target = document.createElement('button');
target.className = 'absolute bg-gradient-to-br from-red-500 to-red-600 rounded-full shadow-2xl fading';
target.style.width = `${SIZE}px`;
target.style.height = `${SIZE}px`;
target.style.left = `${x}px`;
target.style.top = `${y}px`;
arena.appendChild(target);
target.addEventListener('click', () => {
target.remove();
score++;
scoreEl.textContent = `${score} alvos`;
notify('running', { score });
});
setTimeout(() => target.remove(), 1500);
}
function endGame() {
gameActive = false;
clearInterval(spawnInterval);
clearInterval(countdown);
arena.classList.add('hidden');
finalScoreEl.textContent = `Você clicou ${score} alvos em 30 segundos!`;
banner.classList.remove('hidden');
lucide.createIcons();
notify('success', { score });
notify('completed', { score });
}

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse - Velocidade</title>
<link rel="stylesheet" href="../../shared/letramento.css">
<script src="../../shared/lucide.js"></script>
<style>
body { overflow: hidden; height: 100vh; width: 100vw; }
@keyframes fadeOut { to { opacity: 0; transform: scale(0); } }
.fading { animation: fadeOut 1.5s linear forwards; }
</style>
</head>
<body class="bg-gradient-to-br from-brand-50 to-gray-100 flex items-center justify-center h-screen w-screen overflow-hidden p-4">
<div class="w-full h-full flex flex-col">
<div class="bg-white/95 backdrop-blur-sm border border-white/30 rounded-xl shadow-xl flex-1 flex flex-col overflow-hidden">
<div class="flex items-center justify-center gap-4 p-4 border-b border-white/20">
<i data-lucide="zap" class="w-12 h-12 text-red-600"></i>
<h2 class="text-2xl font-bold text-gray-800">Clique rápido antes que desapareçam!</h2>
</div>
<div class="flex-1 p-6 overflow-hidden">
<div id="arena" class="relative h-full bg-white/50 rounded-xl border-2 border-gray-200 shadow-inner overflow-hidden"></div>
<div id="successBanner" class="hidden mt-6 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-8 flex items-center justify-center gap-4">
<i data-lucide="check-circle" class="text-green-600 w-16 h-16"></i>
<div>
<h2 class="text-4xl font-bold text-green-700 mb-2">Muito bem!</h2>
<p class="text-xl text-gray-700" id="finalScore"></p>
</div>
</div>
</div>
</div>
</div>
<script src="./activity.js"></script>
<script>lucide.createIcons();</script>
</body>
</html>

View File

@@ -0,0 +1,152 @@
export const MOUSE_ATIVIDADES_REGISTRY = {
'mouse-basico': {
id: 'mouse-basico',
titulo: 'Mouse Básico',
descricao: 'Aprenda a mover, clicar e fazer duplo clique.',
categoria: 'mouse',
dificuldade: 'iniciante',
duracao: 3,
htmlFile: '/atividades/letramento/mouse/mouse-basico/index.html',
proxima: 'mouse-precisao',
passos: [
{ id: 1, label: 'Mover' },
{ id: 2, label: 'Clicar' },
{ id: 3, label: 'Duplo clique' },
],
},
'mouse-precisao': {
id: 'mouse-precisao',
titulo: 'Precisão com Mouse',
descricao: 'Clique no centro de alvos que diminuem de tamanho.',
categoria: 'mouse',
dificuldade: 'iniciante',
duracao: 3,
htmlFile: '/atividades/letramento/mouse/mouse-precisao/index.html',
proxima: 'mouse-clique-multiplo',
passos: [
{ id: 1, label: 'Alvo 1' },
{ id: 2, label: 'Alvo 2' },
{ id: 3, label: 'Alvo 3' },
{ id: 4, label: 'Alvo 4' },
{ id: 5, label: 'Alvo 5' },
],
},
// 'mouse-controle': {
// id: 'mouse-controle',
// titulo: 'Controle do Mouse',
// descricao: 'Siga o caminho sem sair da linha.',
// categoria: 'mouse',
// dificuldade: 'iniciante',
// duracao: 3,
// htmlFile: '/atividades/letramento/mouse/mouse-controle/index.html',
// proxima: 'mouse-clique-multiplo',
// passos: [
// { id: 1, label: 'Seguir caminho' },
// ],
// },
'mouse-clique-multiplo': {
id: 'mouse-clique-multiplo',
titulo: 'Múltiplos Cliques',
descricao: 'Clique em vários alvos que aparecem ao mesmo tempo.',
categoria: 'mouse',
dificuldade: 'iniciante',
duracao: 2,
htmlFile: '/atividades/letramento/mouse/mouse-clique-multiplo/index.html',
proxima: 'mouse-sequencia',
passos: [
{ id: 1, label: 'Clicar todos' },
],
},
'mouse-sequencia': {
id: 'mouse-sequencia',
titulo: 'Sequência Numérica',
descricao: 'Clique nos números na ordem correta: 1, 2, 3...',
categoria: 'mouse',
dificuldade: 'intermediario',
duracao: 3,
htmlFile: '/atividades/letramento/mouse/mouse-sequencia/index.html',
proxima: 'mouse-velocidade',
passos: [
{ id: 1, label: '1' },
{ id: 2, label: '2' },
{ id: 3, label: '3' },
{ id: 4, label: '4' },
{ id: 5, label: '5' },
],
},
'mouse-velocidade': {
id: 'mouse-velocidade',
titulo: 'Velocidade e Reflexo',
descricao: 'Clique o máximo de alvos em 30 segundos.',
categoria: 'mouse',
dificuldade: 'intermediario',
duracao: 2,
htmlFile: '/atividades/letramento/mouse/mouse-velocidade/index.html',
proxima: 'mouse-botao-direito',
passos: [
{ id: 1, label: 'Desafio' },
],
},
'mouse-botao-direito': {
id: 'mouse-botao-direito',
titulo: 'Botão Direito',
descricao: 'Aprenda a usar o botão direito e menu de contexto.',
categoria: 'mouse',
dificuldade: 'intermediario',
duracao: 2,
htmlFile: '/atividades/letramento/mouse/mouse-botao-direito/index.html',
proxima: 'mouse-arrastar',
passos: [
{ id: 1, label: 'Contexto' },
{ id: 2, label: 'Selecionar' },
],
},
'mouse-arrastar': {
id: 'mouse-arrastar',
titulo: 'Arrastar e Soltar',
descricao: 'Aprenda a arrastar arquivos para uma pasta.',
categoria: 'mouse',
dificuldade: 'intermediario',
duracao: 3,
htmlFile: '/atividades/letramento/mouse/mouse-arrastar/index.html',
proxima: 'mouse-desenhar',
passos: [
{ id: 1, label: 'Arquivo 1' },
{ id: 2, label: 'Arquivo 2' },
{ id: 3, label: 'Arquivo 3' },
],
},
'mouse-desenhar': {
id: 'mouse-desenhar',
titulo: 'Desenhar Traçados',
descricao: 'Siga a linha pontilhada desenhando com o mouse.',
categoria: 'mouse',
dificuldade: 'avancado',
duracao: 4,
htmlFile: '/atividades/letramento/mouse/mouse-desenhar/index.html',
proxima: 'mouse-completo',
passos: [
{ id: 1, label: 'Traçar círculo' },
],
},
'mouse-completo': {
id: 'mouse-completo',
titulo: 'Desafio Completo',
descricao: 'Combinação de todas as habilidades do mouse.',
categoria: 'mouse',
dificuldade: 'avancado',
duracao: 5,
htmlFile: '/atividades/letramento/mouse/mouse-completo/index.html',
proxima: null,
passos: [
{ id: 1, label: 'Precisão' },
{ id: 2, label: 'Sequência' },
{ id: 3, label: 'Trilha' },
{ id: 4, label: 'Múltiplos' },
{ id: 5, label: 'Contexto' },
{ id: 6, label: 'Organizar' },
{ id: 7, label: 'Desenhar' },
{ id: 8, label: 'Final' },
],
},
};