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:
@@ -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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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>
|
||||
212
app/src/atividades/letramento/mouse/mouse-basico/activity.js
Normal file
212
app/src/atividades/letramento/mouse/mouse-basico/activity.js
Normal 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);
|
||||
68
app/src/atividades/letramento/mouse/mouse-basico/index.html
Normal file
68
app/src/atividades/letramento/mouse/mouse-basico/index.html
Normal 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>
|
||||
@@ -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');
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
@@ -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>
|
||||
592
app/src/atividades/letramento/mouse/mouse-completo/activity.js
Normal file
592
app/src/atividades/letramento/mouse/mouse-completo/activity.js
Normal 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();
|
||||
@@ -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>
|
||||
144
app/src/atividades/letramento/mouse/mouse-controle/activity.js
Normal file
144
app/src/atividades/letramento/mouse/mouse-controle/activity.js
Normal 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 });
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
@@ -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>
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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>
|
||||
152
app/src/atividades/letramento/mouse/mouseRegistry.js
Normal file
152
app/src/atividades/letramento/mouse/mouseRegistry.js
Normal 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' },
|
||||
],
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user