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>
285
.gitignore
vendored
@@ -1,138 +1,147 @@
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
deploy_homolog.sh
|
||||
.vscode/settings.json
|
||||
|
||||
/jupyter/.venv
|
||||
|
||||
/SDD
|
||||
app/.vscode/settings.json
|
||||
|
||||
app/specs
|
||||
18
LICENSE
@@ -1,18 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 educacacao
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
542
README.md
@@ -1,3 +1,541 @@
|
||||
# plataforma-edu
|
||||
# Decoda - Plataforma Educacional
|
||||
|
||||
Repositório da plataforma de Educação do Núcleo de Técnologoia
|
||||
> Plataforma para ensinar lógica de programação através de jogos educativos e blocos visuais.
|
||||
|
||||
## 📋 Índice
|
||||
|
||||
- [Sobre o Projeto](#sobre-o-projeto)
|
||||
- [Arquitetura](#arquitetura)
|
||||
- [Pré-requisitos](#pré-requisitos)
|
||||
- [Instalação e Build](#instalação-e-build)
|
||||
- [Opção 1: Docker Compose (Recomendado)](#opção-1-docker-compose-recomendado)
|
||||
- [Opção 2: Desenvolvimento Local](#opção-2-desenvolvimento-local)
|
||||
- [Estrutura do Projeto](#estrutura-do-projeto)
|
||||
- [Git Flow](#git-flow)
|
||||
- [Comandos Úteis](#comandos-úteis)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Contribuindo](#contribuindo)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Sobre o Projeto
|
||||
|
||||
O **Decoda** é uma plataforma educacional que oferece:
|
||||
|
||||
- 🎮 **Jogos educativos** interativos para aprender programação
|
||||
- 📚 **Documentação completa** para educadores e desenvolvedores
|
||||
- 🧩 **Programação visual** com blocos arrastar-e-soltar (sem necessidade de sintaxe), utilizando Blockly.
|
||||
- 🆓 **100% gratuito** e sem necessidade de cadastro
|
||||
|
||||
### Tecnologias Principais
|
||||
|
||||
- **Frontend (App):** React + Vite + Blockly
|
||||
- **Documentação:** Docusaurus
|
||||
- **Servidor Web:** Nginx
|
||||
- **Containerização:** Docker + Docker Compose
|
||||
|
||||
---
|
||||
|
||||
## Arquitetura
|
||||
|
||||
O projeto é dividido em 3 serviços principais:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Nginx Proxy (porta 80) │
|
||||
│ │
|
||||
│ /app/ → App (React/Vite) │
|
||||
│ /docs/ → Docs (Docusaurus) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **app/** - Aplicação React principal com os jogos
|
||||
- **docs/** - Documentação educacional e técnica
|
||||
- **proxy** - Nginx fazendo roteamento entre app e docs
|
||||
|
||||
---
|
||||
|
||||
## ✅ Pré-requisitos
|
||||
|
||||
### Para executar com Docker (Recomendado)
|
||||
|
||||
Você precisa apenas de:
|
||||
|
||||
- **Docker** (versão 20.10 ou superior)
|
||||
- **Docker Compose** (versão 2.0 ou superior)
|
||||
|
||||
> **💡 Não precisa instalar Node.js, npm ou qualquer outra ferramenta!**
|
||||
|
||||
#### Verificar se Docker está instalado
|
||||
|
||||
```bash
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
Se não tiver instalado, siga os guias oficiais:
|
||||
|
||||
- [Instalar Docker](https://docs.docker.com/get-docker/)
|
||||
- Docker Compose já vem incluído nas versões recentes do Docker
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Instalação e Build
|
||||
|
||||
### Opção 1: Docker Compose (Recomendado)
|
||||
|
||||
Esta é a forma mais simples e não requer conhecimento de React ou Node.js.
|
||||
|
||||
#### 1. Clone o repositório
|
||||
|
||||
```bash
|
||||
git clone https://git.mtst.tec.br/rui.moraes/plataforma-edu.git
|
||||
cd plataforma-edu
|
||||
```
|
||||
|
||||
#### 2. Build e execução
|
||||
|
||||
Execute um único comando para construir e iniciar tudo:
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
**O que esse comando faz:**
|
||||
|
||||
- `docker compose up` - Inicia os serviços
|
||||
- `--build` - Reconstrói as imagens se houver mudanças
|
||||
- `-d` - Roda em segundo plano (detached mode)
|
||||
|
||||
#### 3. Aguarde o build
|
||||
|
||||
O primeiro build pode demorar **5-10 minutos** dependendo da sua internet e máquina.
|
||||
|
||||
Você verá logs parecidos com:
|
||||
|
||||
```
|
||||
[+] Building 245.3s
|
||||
=> [app] downloading dependencies...
|
||||
=> [app] building application...
|
||||
=> [docs] downloading dependencies...
|
||||
=> [docs] building documentation...
|
||||
```
|
||||
|
||||
#### 4. Acesse a aplicação
|
||||
|
||||
Depois que o build terminar:
|
||||
|
||||
- **Aplicação:** http://localhost
|
||||
- **Documentação:** http://localhost/docs
|
||||
|
||||
#### 5. Verificar status
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
Você deve ver algo como:
|
||||
|
||||
```
|
||||
NAME STATUS PORTS
|
||||
plataforma-edu-app-1 Up 2 minutes
|
||||
plataforma-edu-docs-1 Up 2 minutes
|
||||
plataforma-edu-proxy-1 Up 2 minutes 0.0.0.0:80->80/tcp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Opção 2: Desenvolvimento Local
|
||||
|
||||
Para desenvolvedores que querem editar o código e ver mudanças em tempo real.
|
||||
|
||||
#### Pré-requisitos adicionais
|
||||
|
||||
- **Node.js** 20.x ou superior
|
||||
- **pnpm** (gerenciador de pacotes)
|
||||
|
||||
#### Instalar pnpm
|
||||
|
||||
```bash
|
||||
npm install -g pnpm
|
||||
```
|
||||
|
||||
#### Rodar App (React)
|
||||
|
||||
```bash
|
||||
cd app
|
||||
pnpm install
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
A aplicação estará disponível em http://localhost:5173
|
||||
|
||||
#### Rodar Documentação (Docusaurus)
|
||||
|
||||
Em outro terminal:
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
pnpm install
|
||||
pnpm run start
|
||||
```
|
||||
|
||||
A documentação estará disponível em http://localhost:3000
|
||||
|
||||
---
|
||||
|
||||
## 📁 Estrutura do Projeto
|
||||
|
||||
```
|
||||
plataforma-edu/
|
||||
├── app/ # Aplicação React principal
|
||||
│ ├── src/ # Código fonte
|
||||
│ │ ├── pages/ # Páginas (Home, Educadores, FAQ, etc)
|
||||
│ │ ├── games/ # Jogos educativos
|
||||
│ │ ├── components/ # Componentes reutilizáveis
|
||||
│ │ └── styles/ # Estilos globais
|
||||
│ ├── public/ # Arquivos estáticos
|
||||
│ ├── Dockerfile # Build da imagem Docker
|
||||
│ ├── package.json # Dependências npm
|
||||
│ └── vite.config.js # Configuração do Vite
|
||||
│
|
||||
├── docs/ # Documentação Docusaurus
|
||||
│ ├── docs/ # Documentação para desenvolvedores
|
||||
│ ├── edu/ # Guias para educadores
|
||||
│ ├── src/ # Componentes customizados
|
||||
│ ├── Dockerfile # Build da imagem Docker
|
||||
│ ├── package.json # Dependências npm
|
||||
│ └── docusaurus.config.js # Configuração
|
||||
│
|
||||
├── docker-compose.yaml # Orquestração dos containers
|
||||
├── nginx.conf # Configuração do proxy Nginx
|
||||
└── README.md # Este arquivo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌳 Git Flow
|
||||
|
||||
### Fluxo de Desenvolvimento
|
||||
|
||||
```mermaid
|
||||
gitGraph
|
||||
commit
|
||||
branch develop
|
||||
checkout develop
|
||||
commit
|
||||
branch feature/nova-funcionalidade
|
||||
checkout feature/nova-funcionalidade
|
||||
commit id: "Implementa funcionalidade A"
|
||||
commit id: "Ajusta funcionalidade A"
|
||||
checkout develop
|
||||
merge feature/nova-funcionalidade
|
||||
commit id: "Prepara para um novo pacote"
|
||||
checkout main
|
||||
merge develop
|
||||
commit id: "Release v1.0"
|
||||
```
|
||||
|
||||
### Fluxo Simplificado
|
||||
|
||||
```
|
||||
1. Criar Feature
|
||||
git checkout -b feature/xyz (a partir de develop)
|
||||
↓
|
||||
2. Desenvolver e Commitar
|
||||
git commit -m "feat: descrição"
|
||||
↓
|
||||
3. Push e PR para Develop
|
||||
git push origin feature/xyz
|
||||
(Abrir PR no GitHub)
|
||||
↓
|
||||
4. Code Review + Merge em Develop
|
||||
Aprovação e merge automático
|
||||
↓
|
||||
5. PR de Develop para Main
|
||||
(Abrir PR: develop → main)
|
||||
↓
|
||||
6. Merge em Main
|
||||
Release pronta para produção
|
||||
```
|
||||
|
||||
### Regras e Configurações
|
||||
|
||||
Para informações detalhadas sobre:
|
||||
- ✅ Como criar branches (a partir de develop)
|
||||
- ✅ Padrão de commits
|
||||
- ✅ Regras de PR
|
||||
- ✅ Deploy em homologação com script
|
||||
- ✅ Variáveis de ambiente e token GitHub
|
||||
|
||||
**👉 Consulte: [README-HOMOLOGACAO.md](README-HOMOLOGACAO.md)**
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Comandos Úteis
|
||||
|
||||
### Docker Compose
|
||||
|
||||
#### Iniciar aplicação
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
#### Parar aplicação
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
#### Ver logs em tempo real
|
||||
|
||||
```bash
|
||||
# Todos os serviços
|
||||
docker compose logs -f
|
||||
|
||||
# Apenas app
|
||||
docker compose logs -f app
|
||||
|
||||
# Apenas documentação
|
||||
docker compose logs -f docs
|
||||
|
||||
# Apenas proxy
|
||||
docker compose logs -f proxy
|
||||
```
|
||||
|
||||
#### Rebuild completo (após mudanças no código)
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
#### Rebuild apenas um serviço
|
||||
|
||||
```bash
|
||||
docker compose up --build app -d
|
||||
```
|
||||
|
||||
#### Verificar status
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
#### Entrar dentro de um container
|
||||
|
||||
```bash
|
||||
# App
|
||||
docker compose exec app sh
|
||||
|
||||
# Docs
|
||||
docker compose exec docs sh
|
||||
```
|
||||
|
||||
#### Remover tudo (containers, volumes, imagens)
|
||||
|
||||
```bash
|
||||
docker compose down --volumes --rmi all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Erro: "port 80 already in use"
|
||||
|
||||
**Problema:** Outra aplicação está usando a porta 80.
|
||||
|
||||
**Solução:**
|
||||
|
||||
```bash
|
||||
# Linux/Mac - Ver o que está usando a porta 80
|
||||
sudo lsof -i :80
|
||||
|
||||
# Parar o serviço (exemplo com Apache)
|
||||
sudo systemctl stop apache2
|
||||
```
|
||||
|
||||
Ou edite `docker-compose.yaml` para usar outra porta:
|
||||
|
||||
```yaml
|
||||
proxy:
|
||||
ports:
|
||||
- "8080:80" # Mude para 8080 ou qualquer porta livre
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Erro: "COPY nginx-spa.conf: not found"
|
||||
|
||||
**Problema:** Arquivo `nginx-spa.conf` não está no diretório `app/`.
|
||||
|
||||
**Solução:**
|
||||
|
||||
```bash
|
||||
# Certifique-se de que o arquivo está na pasta correta
|
||||
ls app/nginx-spa.conf
|
||||
|
||||
# Se não estiver, mova da raiz
|
||||
mv nginx-spa.conf app/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Erro: "Minimum Node.js version not met"
|
||||
|
||||
**Problema:** O Dockerfile está usando Node.js 18, mas o projeto requer Node 20.
|
||||
|
||||
**Solução:** Os Dockerfiles já foram corrigidos para usar Node 20. Se ainda encontrar este erro:
|
||||
|
||||
```bash
|
||||
# Rebuild forçando recriação das imagens
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Build muito lento
|
||||
|
||||
**Dicas para acelerar:**
|
||||
|
||||
1. **Use cache de build do Docker:**
|
||||
|
||||
```bash
|
||||
# Docker irá reusar layers anteriores
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
2. **Aumente recursos do Docker:**
|
||||
|
||||
- Docker Desktop → Settings → Resources
|
||||
- Aumente CPU e Memória
|
||||
|
||||
3. **Limpe imagens antigas:**
|
||||
```bash
|
||||
docker system prune -a
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Acesso retorna 403 ao acessar /docs
|
||||
|
||||
**Problema:** Configuração incorreta do proxy Nginx.
|
||||
|
||||
**Solução:** Verifique se `nginx.conf` tem a barra final no `proxy_pass`:
|
||||
|
||||
```nginx
|
||||
location /docs/ {
|
||||
proxy_pass http://docs/;
|
||||
}
|
||||
```
|
||||
|
||||
Se necessário, rebuild:
|
||||
|
||||
```bash
|
||||
docker compose restart proxy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Mudanças no código não aparecem
|
||||
|
||||
**Problema:** Docker está usando build antigo em cache.
|
||||
|
||||
**Solução:**
|
||||
|
||||
```bash
|
||||
# Rebuild sem cache
|
||||
docker compose build --no-cache app
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Fluxo de Deploy em Produção
|
||||
|
||||
### 1. Preparação
|
||||
|
||||
```bash
|
||||
# Garantir que está na branch correta
|
||||
git checkout main
|
||||
git pull origin main
|
||||
```
|
||||
|
||||
### 2. Build
|
||||
|
||||
```bash
|
||||
# Rebuild completo sem cache
|
||||
docker compose build --no-cache
|
||||
```
|
||||
|
||||
### 3. Deploy
|
||||
|
||||
```bash
|
||||
# Parar versão antiga
|
||||
docker compose down
|
||||
|
||||
# Iniciar nova versão
|
||||
docker compose up -d
|
||||
|
||||
# Verificar logs
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
### 4. Verificação
|
||||
|
||||
```bash
|
||||
# Checar se todos os containers estão rodando
|
||||
docker compose ps
|
||||
|
||||
# Testar endpoints
|
||||
curl http://localhost
|
||||
curl http://localhost/docs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Variáveis de Ambiente
|
||||
|
||||
O projeto atualmente não usa variáveis de ambiente complexas, mas se precisar:
|
||||
|
||||
Crie um arquivo `.env` na raiz:
|
||||
|
||||
```env
|
||||
# Exemplo
|
||||
APP_PORT=80
|
||||
NODE_ENV=production
|
||||
```
|
||||
|
||||
E referencie no `docker-compose.yaml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
env_file:
|
||||
- .env
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contribuindo
|
||||
|
||||
1. Fork o projeto
|
||||
2. Crie uma branch para sua feature (`git checkout -b feature/nova-funcionalidade`)
|
||||
3. Commit suas mudanças (`git commit -m 'Adiciona nova funcionalidade'`)
|
||||
4. Push para a branch (`git push origin feature/nova-funcionalidade`)
|
||||
5. Abra um Pull Request
|
||||
|
||||
---
|
||||
|
||||
## Para Educadores
|
||||
|
||||
Se você é educador e quer usar a plataforma, acesse:
|
||||
|
||||
- http://localhost/educadores - Guia de como começar
|
||||
- http://localhost/docs/edu - Documentação pedagógica completa
|
||||
|
||||
---
|
||||
|
||||
**Desenvolvido com ❤️ para educação em programação**
|
||||
|
||||
1
app/.env.offline
Normal file
@@ -0,0 +1 @@
|
||||
VITE_IS_OFFLINE=true
|
||||
156
app/.gitignore
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Letramento activities (auto-generated from src)
|
||||
public/letramento/
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Sveltekit cache directory
|
||||
.svelte-kit/
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Firebase cache directory
|
||||
.firebase/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v3
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# Vite logs files
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
# Ignorar build local de docs
|
||||
docs/build/
|
||||
docs/.docusaurus/
|
||||
docs/node_modules
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
TODO.md
|
||||
.vercel
|
||||
|
||||
# Arquivos de rastreamento de refatoração (MANTER COMMITADOS)
|
||||
# docs/JSDOC_GUIDELINE.md - Guideline para implementação
|
||||
# docs/JDocs_Refatoracao.md - Plano de refatoração JSDoc com progresso
|
||||
|
||||
.specify
|
||||
.github
|
||||
.specs
|
||||
.vscode
|
||||
13
app/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"chat.promptFilesRecommendations": {
|
||||
"speckit.constitution": true,
|
||||
"speckit.specify": true,
|
||||
"speckit.plan": true,
|
||||
"speckit.tasks": true,
|
||||
"speckit.implement": true
|
||||
},
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
".specify/scripts/bash/": true,
|
||||
".specify/scripts/powershell/": true
|
||||
}
|
||||
}
|
||||
25
app/Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ARG GIT_COMMIT_HASH=unknown
|
||||
ARG APP_VERSION=1.1.2
|
||||
ENV VITE_APP_VERSION=$APP_VERSION
|
||||
ENV VITE_GIT_HASH=$GIT_COMMIT_HASH
|
||||
|
||||
RUN npm install -g pnpm
|
||||
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN echo "{\"version\": \"$APP_VERSION\", \"commit\": \"$GIT_COMMIT_HASH\", \"buildDate\": \"$(date)\"}" > public/version.json
|
||||
|
||||
RUN pnpm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
COPY nginx-spa.conf /etc/nginx/conf.d/default.conf
|
||||
32
app/Makefile
Normal file
@@ -0,0 +1,32 @@
|
||||
# Variáveis
|
||||
IMAGE_NAME = decoda-app
|
||||
VERSION = $(shell grep 'ARG APP_VERSION=' Dockerfile | cut -d'=' -f2)
|
||||
|
||||
.PHONY: build run stop clean
|
||||
|
||||
# Build da imagem docker
|
||||
build:
|
||||
@echo "Construindo imagem $(IMAGE_NAME):$(VERSION)..."
|
||||
docker build --build-arg APP_VERSION=$(VERSION) -t $(IMAGE_NAME):$(VERSION) .
|
||||
docker tag $(IMAGE_NAME):$(VERSION) $(IMAGE_NAME):latest
|
||||
|
||||
# Executar o container
|
||||
run:
|
||||
@echo "Iniciando container $(IMAGE_NAME):$(VERSION) na porta 8080..."
|
||||
docker run -d --name $(IMAGE_NAME) -p 8080:80 $(IMAGE_NAME):$(VERSION)
|
||||
@echo "Aplicação disponível em http://localhost:8080"
|
||||
|
||||
# Parar e remover o container
|
||||
stop:
|
||||
@echo "Parando container..."
|
||||
docker stop $(IMAGE_NAME) || true
|
||||
docker rm $(IMAGE_NAME) || true
|
||||
|
||||
# Ver logs
|
||||
logs:
|
||||
docker logs -f $(IMAGE_NAME)
|
||||
|
||||
# Limpar imagens antigas
|
||||
clean:
|
||||
@echo "Removendo imagem $(IMAGE_NAME):$(VERSION)..."
|
||||
docker rmi $(IMAGE_NAME):$(VERSION) $(IMAGE_NAME):latest || true
|
||||
101
app/README.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Decoda - App
|
||||
|
||||
Uma plataforma educacional de programação visual desenvolvida em React, combinando Blockly, Phaser e tours interativos para ensinar lógica de programação através de jogos.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Sobre o Projeto
|
||||
|
||||
Decoda é uma plataforma educacional inspirada no [Blockly Games](https://blockly.games/) do Google, expandida com recursos modernos e jogos adicionais. A plataforma combina programação visual (Blockly) com jogos interativos para ensinar conceitos fundamentais de programação.
|
||||
|
||||
### Jogos Disponíveis
|
||||
|
||||
- **Automato** - Navegação em labirinto com lógica de movimentação
|
||||
- **Bird** - Jogo de coleta com física e animações
|
||||
- **Semáforo** - Controle de trânsito e sequências lógicas
|
||||
- **Turtle** - Desenho programático (Logo graphics)
|
||||
- **Motoca** - Jogo de esquiva e timing
|
||||
- **Emoji Match** - Jogo de memória e correspondência
|
||||
- **Detector de Sentimentos** - Análise de texto com IA
|
||||
- **Playground** - Editor livre para experimentação
|
||||
|
||||
### Características
|
||||
|
||||
- **Programação Visual**: Editor Blockly com blocos customizados
|
||||
- **Tours Guiados**: Sistema completo de onboarding com Shepherd.js
|
||||
- **Responsivo**: Interface adaptada para desktop e mobile
|
||||
- **Persistência**: Salvamento automático do progresso
|
||||
- **Otimizado**: Bundle inicial de apenas 5.89 kB (98.4% de redução)
|
||||
|
||||
## 🚀 Desenvolvimento
|
||||
|
||||
### Pré-requisitos
|
||||
|
||||
- Node.js 20 ou superior
|
||||
- pnpm
|
||||
|
||||
### Instalação e Execução
|
||||
|
||||
```bash
|
||||
# Clone o repositório
|
||||
git clone https://git.mtst.tec.br/rui.moraes/plataforma-edu.git
|
||||
cd plataforma-edu/app
|
||||
|
||||
# Instale as dependências
|
||||
pnpm install
|
||||
|
||||
# Execute em modo de desenvolvimento
|
||||
pnpm run dev
|
||||
|
||||
# Acesse no navegador
|
||||
http://localhost:5173
|
||||
```
|
||||
|
||||
## 📄 Scripts Disponíveis
|
||||
|
||||
```bash
|
||||
pnpm dev # Executa aplicação em desenvolvimento (porta 5173)
|
||||
pnpm build # Build de produção
|
||||
pnpm preview # Preview do build de produção
|
||||
pnpm test # Executa testes com Vitest
|
||||
pnpm lint # Verifica código com ESLint
|
||||
```
|
||||
|
||||
## 🏗️ Tecnologias
|
||||
|
||||
- **React 19** - Framework UI com lazy loading
|
||||
- **Blockly 12.3** - Editor de programação visual
|
||||
- **Phaser 3.90** - Engine para jogos 2D
|
||||
- **Shepherd.js 14.5** - Tours guiados interativos
|
||||
- **Vite 7.1** - Build tool otimizado
|
||||
- **TailwindCSS 3.4** - Estilização utilitária
|
||||
|
||||
## 📊 Performance
|
||||
|
||||
- **Lazy Loading**: Componentes carregados sob demanda
|
||||
- **Code Splitting**: Vendors separados (Blockly 3.5 MB, Phaser 850 KB)
|
||||
- **Tree Shaking**: Imports otimizados de blockly/core
|
||||
- **Image Optimization**: Compressão automática com Sharp
|
||||
|
||||
**Resultado**: Bundle inicial reduzido de 3.870 MB para 5.89 kB (98.4% de redução)
|
||||
|
||||
## 📚 Documentação
|
||||
|
||||
A documentação completa do projeto está em `/docs` na raiz do repositório.
|
||||
|
||||
Para rodar a documentação localmente:
|
||||
|
||||
```bash
|
||||
cd ../docs
|
||||
pnpm install
|
||||
pnpm start
|
||||
```
|
||||
|
||||
Acesse em: http://localhost:3000
|
||||
|
||||
---
|
||||
|
||||
**Desenvolvido com ❤️ para educação em programação**
|
||||
29
app/THIRD_PARTY_NOTICES.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Third-Party Notices
|
||||
|
||||
Este arquivo centraliza os avisos de dependências de terceiros usadas no app.
|
||||
|
||||
## Dependências Apache 2.0
|
||||
|
||||
### Blockly
|
||||
|
||||
- Pacote: `blockly`
|
||||
- Versão usada no app: `12.3.1`
|
||||
- Licença declarada: `Apache-2.0`
|
||||
- Projeto: https://developers.google.com/blockly/
|
||||
- Repositório: https://github.com/google/blockly
|
||||
- Texto da licença Apache 2.0 incluído em: `licenses/APACHE-2.0.txt`
|
||||
- Referência oficial da licença do projeto: https://github.com/google/blockly/blob/master/LICENSE
|
||||
|
||||
### JS-Interpreter
|
||||
|
||||
- Pacote: `js-interpreter`
|
||||
- Versão usada no app: `6.0.1`
|
||||
- Licença declarada: `Apache-2.0`
|
||||
- Repositório: https://github.com/NeilFraser/JS-Interpreter
|
||||
- Texto da licença original do pacote incluído em: `licenses/js-interpreter-LICENSE.txt`
|
||||
- Texto da Apache 2.0 incluído em: `licenses/APACHE-2.0.txt`
|
||||
|
||||
## Observações de distribuição
|
||||
|
||||
- Ao distribuir o app (source ou binário), inclua este arquivo junto com os arquivos da pasta `licenses/`.
|
||||
- Se houver modificação direta em código de dependência Apache 2.0, registre a alteração no release/changelog.
|
||||
BIN
app/assets/icon.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
1
app/eslint-report.json
Normal file
33
app/eslint.config.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(["dist"]),
|
||||
{
|
||||
plugins: {
|
||||
"react-hooks": reactHooks,
|
||||
"react-refresh": reactRefresh,
|
||||
},
|
||||
files: ["**/*.{js,jsx}"],
|
||||
extends: [js.configs.recommended],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: "module",
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// Re-enable unused-vars as warnings; underscore-prefixed identifiers are ignored.
|
||||
"no-unused-vars": [
|
||||
"warn",
|
||||
{ "vars": "all", "args": "after-used", "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "ignoreRestSiblings": true }
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
14
app/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/puzzle.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<title>Decoda</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
201
app/licenses/APACHE-2.0.txt
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2013 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
23
app/licenses/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Licenças de Terceiros (App)
|
||||
|
||||
Este diretório agrupa os textos de licença necessários para redistribuição.
|
||||
|
||||
## Conteúdo atual
|
||||
|
||||
- APACHE-2.0.txt
|
||||
- Texto integral da Apache License 2.0.
|
||||
- Usado para dependências Apache 2.0 no app (ex.: Blockly e js-interpreter).
|
||||
|
||||
- js-interpreter-LICENSE.txt
|
||||
- Cópia da licença do pacote js-interpreter utilizado no app.
|
||||
|
||||
## Blockly
|
||||
|
||||
No pacote instalado (`blockly@12.3.1`) não foi encontrado arquivo LICENSE/NOTICE no diretório do artefato distribuído pelo gerenciador de pacotes nesta instalação.
|
||||
|
||||
Para rastreabilidade, use as referências oficiais:
|
||||
|
||||
- https://github.com/google/blockly/blob/master/LICENSE
|
||||
- https://developers.google.com/blockly
|
||||
|
||||
As referências completas também estão em `../THIRD_PARTY_NOTICES.md`.
|
||||
201
app/licenses/js-interpreter-LICENSE.txt
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2013 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
72
app/main.cjs
Normal file
@@ -0,0 +1,72 @@
|
||||
const { app, BrowserWindow, protocol, net } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
// Deve ser chamado antes de app.ready
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: 'app', privileges: { secure: true, standard: true, supportFetchAPI: true } }
|
||||
]);
|
||||
|
||||
app.commandLine.appendSwitch('no-proxy-server');
|
||||
app.commandLine.appendSwitch('enable-gpu-rasterization');
|
||||
app.commandLine.appendSwitch('enable-zero-copy');
|
||||
app.commandLine.appendSwitch('ignore-gpu-blocklist');
|
||||
|
||||
function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 800,
|
||||
show: true, // (Esconde ou mostra o processo de carregamento)
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
}
|
||||
});
|
||||
|
||||
win.setMenuBarVisibility(false);
|
||||
win.maximize();
|
||||
|
||||
if (!app.isPackaged) {
|
||||
win.loadURL('http://localhost:5173');
|
||||
} else {
|
||||
win.loadURL('app://localhost/');
|
||||
}
|
||||
|
||||
win.once('ready-to-show', () => {
|
||||
win.show();
|
||||
});
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
if (app.isPackaged) {
|
||||
const distPath = path.join(__dirname, 'dist');
|
||||
|
||||
protocol.handle('app', (request) => {
|
||||
const url = new URL(request.url);
|
||||
const filePath = path.join(distPath, url.pathname);
|
||||
|
||||
// Se o arquivo exato existir, serve ele
|
||||
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
||||
return net.fetch(pathToFileURL(filePath).href);
|
||||
}
|
||||
|
||||
// Caso contrário, serve index.html (rotas do React Router)
|
||||
return net.fetch(pathToFileURL(path.join(distPath, 'index.html')).href);
|
||||
});
|
||||
}
|
||||
|
||||
createWindow();
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
9
app/nginx-spa.conf
Normal file
@@ -0,0 +1,9 @@
|
||||
server {
|
||||
listen 80;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
128
app/package.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"name": "decoda",
|
||||
"private": true,
|
||||
"description": "Aplicação educacional desenvolvida para ensino de programação básica e letramento digital",
|
||||
"version": "1.1.2",
|
||||
"main": "main.cjs",
|
||||
"homepage": "./",
|
||||
"type": "module",
|
||||
"keywords": [
|
||||
"decoda",
|
||||
"blockly",
|
||||
"education",
|
||||
"programming",
|
||||
"react",
|
||||
"maze",
|
||||
"programação",
|
||||
"visual-programming",
|
||||
"educational-game"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "concurrently -k -n APP \"pnpm run dev:app\"",
|
||||
"dev:app": "pnpm run build:letramento-assets && pnpm run build:letramento-css && vite",
|
||||
"build:letramento-assets": "rm -f src/atividades/letramento/shared/lucide.js && cpr node_modules/lucide/dist/umd/lucide.js src/atividades/letramento/shared/lucide.js",
|
||||
"build:letramento-css": "tailwindcss -c tailwind.letramento.config.js -i src/atividades/letramento/shared/tailwind-input.css -o src/atividades/letramento/shared/letramento.css",
|
||||
"build": "pnpm run build:letramento-assets && pnpm run build:letramento-css && vite build",
|
||||
"build:offline": "pnpm run build:letramento-assets && pnpm run build:letramento-css && vite build --mode offline",
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"coverage": "vitest run --coverage",
|
||||
"test:cache": "node scripts/test-cache.mjs",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"preview:prod": "pnpm run build && serve dist -l 3000",
|
||||
"electron:dev": "electron .",
|
||||
"electron:build": "pnpm run build:offline && electron-builder"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.plataformaedu.decoda",
|
||||
"productName": "Decoda",
|
||||
"icon": "assets/icon.png",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"main.cjs",
|
||||
"package.json"
|
||||
],
|
||||
"directories": {
|
||||
"buildResources": "assets"
|
||||
},
|
||||
"win": {
|
||||
"target": "dir",
|
||||
"icon": "assets/icon.png"
|
||||
},
|
||||
"mac": {
|
||||
"target": "dir",
|
||||
"icon": "assets/icon.png"
|
||||
},
|
||||
"linux": {
|
||||
"target": "dir",
|
||||
"icon": "assets/icon.png"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@blockly/field-angle": "^6.0.3",
|
||||
"@blockly/theme-modern": "^7.0.1",
|
||||
"@codemirror/autocomplete": "6.20.1",
|
||||
"@codemirror/commands": "6.10.3",
|
||||
"@codemirror/lang-javascript": "6.2.5",
|
||||
"@codemirror/language": "6.12.3",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.41.0",
|
||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||
"@tsparticles/confetti": "^3.9.1",
|
||||
"@uiw/react-codemirror": "4.25.9",
|
||||
"acorn": "^8.15.0",
|
||||
"blockly": "^12.3.1",
|
||||
"js-interpreter": "^6.0.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lucide": "^0.546.0",
|
||||
"lucide-react": "^0.546.0",
|
||||
"phaser": "^3.90.0",
|
||||
"phaser3-rex-plugins": "^1.80.16",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^19.2.0",
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-resizable-panels": "^3.0.6",
|
||||
"react-router-dom": "^7.9.4",
|
||||
"react-shepherd": "^6.1.9",
|
||||
"sentiment": "^5.0.2",
|
||||
"shepherd.js": "^14.5.1",
|
||||
"svgo": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.38.0",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.1",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vitest/ui": "4.0.16",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"baseline-browser-mapping": "^2.9.11",
|
||||
"concurrently": "^9.2.1",
|
||||
"cpr": "^3.0.1",
|
||||
"electron": "^42.0.1",
|
||||
"electron-builder": "^26.8.1",
|
||||
"electron-is-dev": "^3.0.1",
|
||||
"eslint": "^9.38.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"fs-extra": "^11.3.4",
|
||||
"globals": "^16.4.0",
|
||||
"jsdom": "^27.4.0",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.8.1",
|
||||
"rollup-plugin-visualizer": "^6.0.5",
|
||||
"serve": "^14.2.5",
|
||||
"sharp": "^0.34.4",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"vite": "^7.1.10",
|
||||
"vite-plugin-image-optimizer": "^2.0.2",
|
||||
"vite-plugin-pwa": "^0.20.1",
|
||||
"vitest": "^4.0.16"
|
||||
}
|
||||
}
|
||||
10176
app/pnpm-lock.yaml
generated
Normal file
5
app/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
packages:
|
||||
- "docs"
|
||||
|
||||
ignoredBuiltDependencies:
|
||||
- sharp
|
||||
6
app/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
149
app/public/examples/contador.json
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"blocks": {
|
||||
"languageVersion": 0,
|
||||
"blocks": [
|
||||
{
|
||||
"type": "variables_set",
|
||||
"id": "Y(q!:xX2SO#^rn0HexpC",
|
||||
"x": 13,
|
||||
"y": 13,
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "uM8{44H2Q1VDW~_9?`d@"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "^UyXu5e[g*($5JUAF#;A",
|
||||
"fields": {
|
||||
"NUM": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "controls_repeat_ext",
|
||||
"id": "f^X_Bt=Su]O/,s59tc~m",
|
||||
"inputs": {
|
||||
"TIMES": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "a=R)5T~o$+o3M(rsRHeb",
|
||||
"fields": {
|
||||
"NUM": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
"DO": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "m`~pK[*KIEa8:8|`#5}(",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "uM8{44H2Q1VDW~_9?`d@"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_arithmetic",
|
||||
"id": "Nlwtya4^j!E:@7?2T;iR",
|
||||
"fields": {
|
||||
"OP": "ADD"
|
||||
},
|
||||
"inputs": {
|
||||
"A": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "S1;j@zuu)[l}n,`/eemq",
|
||||
"fields": {
|
||||
"NUM": 1
|
||||
}
|
||||
},
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "d*E/y,RyI|o|p/IG=]:A",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "uM8{44H2Q1VDW~_9?`d@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"B": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": ":3YAg!)fW7L6jYS/MK3W",
|
||||
"fields": {
|
||||
"NUM": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "text_print",
|
||||
"id": "_Oun9U]i%qCF[t_|u=OE",
|
||||
"inputs": {
|
||||
"TEXT": {
|
||||
"shadow": {
|
||||
"type": "text",
|
||||
"id": "(bDQA05nNhZZwn{K}t4s",
|
||||
"fields": {
|
||||
"TEXT": "abc"
|
||||
}
|
||||
},
|
||||
"block": {
|
||||
"type": "text_join",
|
||||
"id": "Z+479SCK}j`9Z4@vZ]S~",
|
||||
"extraState": {
|
||||
"itemCount": 2
|
||||
},
|
||||
"inputs": {
|
||||
"ADD0": {
|
||||
"block": {
|
||||
"type": "text",
|
||||
"id": "xb~m=t+_:zuIZD9LAq25",
|
||||
"fields": {
|
||||
"TEXT": "Contador: "
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD1": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "uKcvAA!BOkzO4lO7gHeN",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "uM8{44H2Q1VDW~_9?`d@"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "contador",
|
||||
"id": "uM8{44H2Q1VDW~_9?`d@"
|
||||
}
|
||||
]
|
||||
}
|
||||
224
app/public/examples/fatorial.json
Normal file
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"blocks": {
|
||||
"languageVersion": 0,
|
||||
"blocks": [
|
||||
{
|
||||
"type": "variables_set",
|
||||
"id": "set_numero",
|
||||
"x": 50,
|
||||
"y": 50,
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numero"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "input_num",
|
||||
"fields": {
|
||||
"NUM": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "set_fatorial",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "fatorial"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "one",
|
||||
"fields": {
|
||||
"NUM": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "set_i",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "i"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "start_one",
|
||||
"fields": {
|
||||
"NUM": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "controls_repeat_ext",
|
||||
"id": "loop_fatorial",
|
||||
"inputs": {
|
||||
"TIMES": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_numero_times",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numero"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DO": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "update_fatorial",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "fatorial"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_arithmetic",
|
||||
"id": "multiply",
|
||||
"fields": {
|
||||
"OP": "MULTIPLY"
|
||||
},
|
||||
"inputs": {
|
||||
"A": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_fatorial",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "fatorial"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"B": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_i",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "i"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "math_change",
|
||||
"id": "increment_i",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "i"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"DELTA": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "delta_one",
|
||||
"fields": {
|
||||
"NUM": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "text_print",
|
||||
"id": "print_result",
|
||||
"inputs": {
|
||||
"TEXT": {
|
||||
"block": {
|
||||
"type": "text_join",
|
||||
"id": "join_text",
|
||||
"extraState": {
|
||||
"itemCount": 3
|
||||
},
|
||||
"inputs": {
|
||||
"ADD0": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_numero_print",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numero"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD1": {
|
||||
"block": {
|
||||
"type": "text",
|
||||
"id": "text_label",
|
||||
"fields": {
|
||||
"TEXT": "! = "
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD2": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_fatorial_print",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "fatorial"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "numero",
|
||||
"id": "numero"
|
||||
},
|
||||
{
|
||||
"name": "fatorial",
|
||||
"id": "fatorial"
|
||||
},
|
||||
{
|
||||
"name": "i",
|
||||
"id": "i"
|
||||
}
|
||||
]
|
||||
}
|
||||
312
app/public/examples/fibonacci.json
Normal file
@@ -0,0 +1,312 @@
|
||||
{
|
||||
"blocks": {
|
||||
"languageVersion": 0,
|
||||
"blocks": [
|
||||
{
|
||||
"type": "procedures_defreturn",
|
||||
"id": "3C)~7]GT2][Kt@KzYlHc",
|
||||
"x": 13,
|
||||
"y": 38,
|
||||
"extraState": {
|
||||
"params": [
|
||||
{
|
||||
"name": "n",
|
||||
"id": "6`,pL@UC7:`VF:U%^RCu"
|
||||
}
|
||||
]
|
||||
},
|
||||
"icons": {
|
||||
"comment": {
|
||||
"text": "Describe this function...",
|
||||
"pinned": false,
|
||||
"height": 80,
|
||||
"width": 160
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"NAME": "fibonacci"
|
||||
},
|
||||
"inputs": {
|
||||
"STACK": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "W?1~%41vb!^AG%Oczt#K",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "GD2k9YCHmzqmPfXa(|V="
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "e?s1@^g{J1dC-)`k5K3O",
|
||||
"fields": {
|
||||
"NUM": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "quhf01iSD=$cnS@`]Hnz",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": ")7]X14`/^KwNZ[9]iHR/"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "wskI%[r=fTR|znd{9l!U",
|
||||
"fields": {
|
||||
"NUM": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "controls_repeat_ext",
|
||||
"id": "Y1JZ]jy6qm.wcl3Wtj{U",
|
||||
"inputs": {
|
||||
"TIMES": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "uAO5X0`?QM.x`xlm%XO!",
|
||||
"fields": {
|
||||
"NUM": 0
|
||||
}
|
||||
},
|
||||
"block": {
|
||||
"type": "math_arithmetic",
|
||||
"id": "o?i7JTy}==/MI6DS*rUb",
|
||||
"fields": {
|
||||
"OP": "MINUS"
|
||||
},
|
||||
"inputs": {
|
||||
"A": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "T8sO~/s%:362r5V/6sA_",
|
||||
"fields": {
|
||||
"NUM": 1
|
||||
}
|
||||
},
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "UF`[,+F,hh4j0W|*L@~W",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "6`,pL@UC7:`VF:U%^RCu"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"B": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "D}/e~MS,EFFYPVr(bQ#~",
|
||||
"fields": {
|
||||
"NUM": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DO": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "_,!Y_qDuB8i`WCazRd08",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "4;/a7hK%L6KN+}XB0qVj"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "sog*YAl,Yn8Mo.GOeVVv",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "GD2k9YCHmzqmPfXa(|V="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "c#tQYioX:/LbhQ^Eb-ad",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "GD2k9YCHmzqmPfXa(|V="
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "L?8}.KfNpw[Sd)(1wW(p",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": ")7]X14`/^KwNZ[9]iHR/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "=lea:b{-I}JB.Ed[S_PO",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": ")7]X14`/^KwNZ[9]iHR/"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_arithmetic",
|
||||
"id": "@5-VK*).iEqtzdv/mFUR",
|
||||
"fields": {
|
||||
"OP": "ADD"
|
||||
},
|
||||
"inputs": {
|
||||
"A": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "T8sO~/s%:362r5V/6sA_",
|
||||
"fields": {
|
||||
"NUM": 1
|
||||
}
|
||||
},
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "=jr(DI27H.4@{cBq^OAn",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "4;/a7hK%L6KN+}XB0qVj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"B": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "qoJJ{zN7)tVw[ngGun$h",
|
||||
"fields": {
|
||||
"NUM": 2
|
||||
}
|
||||
},
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "6PSiJrCzD;Xu-`jPCE9f",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": ")7]X14`/^KwNZ[9]iHR/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"RETURN": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "904[,pgVzHQrUk4aPFs%",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": ")7]X14`/^KwNZ[9]iHR/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text_print",
|
||||
"id": "Vd~Z/v{*E:@#|m;C$cz7",
|
||||
"x": 13,
|
||||
"y": 513,
|
||||
"inputs": {
|
||||
"TEXT": {
|
||||
"shadow": {
|
||||
"type": "text",
|
||||
"id": "Y4+2yu|,Zxn$EymXN_}t",
|
||||
"fields": {
|
||||
"TEXT": "abc"
|
||||
}
|
||||
},
|
||||
"block": {
|
||||
"type": "procedures_callreturn",
|
||||
"id": "BZhl_r/{_DkJCk@=p^oC",
|
||||
"extraState": {
|
||||
"name": "fibonacci",
|
||||
"params": ["n"]
|
||||
},
|
||||
"inputs": {
|
||||
"ARG0": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "yFz:/Mz=nU{=g-yvnNMb",
|
||||
"fields": {
|
||||
"NUM": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "a",
|
||||
"id": "GD2k9YCHmzqmPfXa(|V="
|
||||
},
|
||||
{
|
||||
"name": "b",
|
||||
"id": ")7]X14`/^KwNZ[9]iHR/"
|
||||
},
|
||||
{
|
||||
"name": "i",
|
||||
"id": "o4{?aDJsw@NJIM4Ls[A)"
|
||||
},
|
||||
{
|
||||
"name": "x",
|
||||
"id": "@AFqRs|{~R-4S[-d=0bB"
|
||||
},
|
||||
{
|
||||
"name": "n",
|
||||
"id": "6`,pL@UC7:`VF:U%^RCu"
|
||||
},
|
||||
{
|
||||
"name": "temp",
|
||||
"id": "4;/a7hK%L6KN+}XB0qVj"
|
||||
}
|
||||
]
|
||||
}
|
||||
254
app/public/examples/maior-numero.json
Normal file
@@ -0,0 +1,254 @@
|
||||
{
|
||||
"blocks": {
|
||||
"languageVersion": 0,
|
||||
"blocks": [
|
||||
{
|
||||
"type": "variables_set",
|
||||
"id": "set_lista",
|
||||
"x": 50,
|
||||
"y": 50,
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numeros"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "lists_create_with",
|
||||
"id": "create_list",
|
||||
"extraState": {
|
||||
"itemCount": 5
|
||||
},
|
||||
"inputs": {
|
||||
"ADD0": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "num_1",
|
||||
"fields": {
|
||||
"NUM": 23
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD1": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "num_2",
|
||||
"fields": {
|
||||
"NUM": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD2": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "num_3",
|
||||
"fields": {
|
||||
"NUM": 42
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD3": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "num_4",
|
||||
"fields": {
|
||||
"NUM": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD4": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "num_5",
|
||||
"fields": {
|
||||
"NUM": 15
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "set_maior",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "maior"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "lists_getIndex",
|
||||
"id": "get_first",
|
||||
"fields": {
|
||||
"MODE": "GET",
|
||||
"WHERE": "FIRST"
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_lista_first",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numeros"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "controls_forEach",
|
||||
"id": "foreach_loop",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numero"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"LIST": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_lista",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numeros"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DO": {
|
||||
"block": {
|
||||
"type": "controls_if",
|
||||
"id": "check_maior",
|
||||
"inputs": {
|
||||
"IF0": {
|
||||
"block": {
|
||||
"type": "logic_compare",
|
||||
"id": "compare_gt",
|
||||
"fields": {
|
||||
"OP": "GT"
|
||||
},
|
||||
"inputs": {
|
||||
"A": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_numero",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numero"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"B": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_maior",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "maior"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DO0": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "update_maior",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "maior"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_numero_update",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numero"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "text_print",
|
||||
"id": "print_result",
|
||||
"inputs": {
|
||||
"TEXT": {
|
||||
"block": {
|
||||
"type": "text_join",
|
||||
"id": "join_text",
|
||||
"extraState": {
|
||||
"itemCount": 2
|
||||
},
|
||||
"inputs": {
|
||||
"ADD0": {
|
||||
"block": {
|
||||
"type": "text",
|
||||
"id": "label",
|
||||
"fields": {
|
||||
"TEXT": "Maior número: "
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD1": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_maior_final",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "maior"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "numeros",
|
||||
"id": "numeros"
|
||||
},
|
||||
{
|
||||
"name": "maior",
|
||||
"id": "maior"
|
||||
},
|
||||
{
|
||||
"name": "numero",
|
||||
"id": "numero"
|
||||
}
|
||||
]
|
||||
}
|
||||
26
app/public/examples/nome.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "Imprimir Nome",
|
||||
"description": "Um programa simples que imprime um nome",
|
||||
"blocks": {
|
||||
"languageVersion": 0,
|
||||
"blocks": [
|
||||
{
|
||||
"type": "text_print",
|
||||
"id": "print_name",
|
||||
"x": 50,
|
||||
"y": 50,
|
||||
"inputs": {
|
||||
"TEXT": {
|
||||
"shadow": {
|
||||
"type": "text",
|
||||
"id": "name_text",
|
||||
"fields": {
|
||||
"TEXT": "João Silva"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
172
app/public/examples/par-impar.json
Normal file
@@ -0,0 +1,172 @@
|
||||
{
|
||||
"blocks": {
|
||||
"languageVersion": 0,
|
||||
"blocks": [
|
||||
{
|
||||
"type": "variables_set",
|
||||
"id": "set_numero",
|
||||
"x": 50,
|
||||
"y": 50,
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numero"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "input_num",
|
||||
"fields": {
|
||||
"NUM": 7
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "controls_if",
|
||||
"id": "check_par",
|
||||
"extraState": {
|
||||
"hasElse": true
|
||||
},
|
||||
"inputs": {
|
||||
"IF0": {
|
||||
"block": {
|
||||
"type": "logic_compare",
|
||||
"id": "compare_mod",
|
||||
"fields": {
|
||||
"OP": "EQ"
|
||||
},
|
||||
"inputs": {
|
||||
"A": {
|
||||
"block": {
|
||||
"type": "math_modulo",
|
||||
"id": "modulo",
|
||||
"inputs": {
|
||||
"DIVIDEND": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_num",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numero"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DIVISOR": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "divisor_2",
|
||||
"fields": {
|
||||
"NUM": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"B": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "zero",
|
||||
"fields": {
|
||||
"NUM": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DO0": {
|
||||
"block": {
|
||||
"type": "text_print",
|
||||
"id": "print_par",
|
||||
"inputs": {
|
||||
"TEXT": {
|
||||
"block": {
|
||||
"type": "text_join",
|
||||
"id": "join_par",
|
||||
"extraState": {
|
||||
"itemCount": 2
|
||||
},
|
||||
"inputs": {
|
||||
"ADD0": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_num_par",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numero"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD1": {
|
||||
"block": {
|
||||
"type": "text",
|
||||
"id": "text_par",
|
||||
"fields": {
|
||||
"TEXT": " é PAR"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ELSE": {
|
||||
"block": {
|
||||
"type": "text_print",
|
||||
"id": "print_impar",
|
||||
"inputs": {
|
||||
"TEXT": {
|
||||
"block": {
|
||||
"type": "text_join",
|
||||
"id": "join_impar",
|
||||
"extraState": {
|
||||
"itemCount": 2
|
||||
},
|
||||
"inputs": {
|
||||
"ADD0": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_num_impar",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "numero"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD1": {
|
||||
"block": {
|
||||
"type": "text",
|
||||
"id": "text_impar",
|
||||
"fields": {
|
||||
"TEXT": " é ÍMPAR"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "numero",
|
||||
"id": "numero"
|
||||
}
|
||||
]
|
||||
}
|
||||
221
app/public/examples/soma-lista.json
Normal file
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"blocks": {
|
||||
"languageVersion": 0,
|
||||
"blocks": [
|
||||
{
|
||||
"type": "variables_set",
|
||||
"id": "set_lista",
|
||||
"x": 50,
|
||||
"y": 50,
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "lista_numeros"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "lists_create_with",
|
||||
"id": "create_list",
|
||||
"extraState": {
|
||||
"itemCount": 5
|
||||
},
|
||||
"inputs": {
|
||||
"ADD0": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "num_1",
|
||||
"fields": {
|
||||
"NUM": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD1": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "num_2",
|
||||
"fields": {
|
||||
"NUM": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD2": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "num_3",
|
||||
"fields": {
|
||||
"NUM": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD3": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "num_4",
|
||||
"fields": {
|
||||
"NUM": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD4": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "num_5",
|
||||
"fields": {
|
||||
"NUM": 12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "set_soma",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "soma"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "zero",
|
||||
"fields": {
|
||||
"NUM": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "controls_forEach",
|
||||
"id": "foreach_loop",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "item"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"LIST": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_lista",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "lista_numeros"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DO": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "update_soma",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "soma"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_arithmetic",
|
||||
"id": "add_item",
|
||||
"fields": {
|
||||
"OP": "ADD"
|
||||
},
|
||||
"inputs": {
|
||||
"A": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_soma",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "soma"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"B": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_item",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "item"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "text_print",
|
||||
"id": "print_result",
|
||||
"inputs": {
|
||||
"TEXT": {
|
||||
"block": {
|
||||
"type": "text_join",
|
||||
"id": "join_text",
|
||||
"extraState": {
|
||||
"itemCount": 2
|
||||
},
|
||||
"inputs": {
|
||||
"ADD0": {
|
||||
"block": {
|
||||
"type": "text",
|
||||
"id": "label",
|
||||
"fields": {
|
||||
"TEXT": "Soma total: "
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD1": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_soma_final",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "soma"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "lista",
|
||||
"id": "lista_numeros"
|
||||
},
|
||||
{
|
||||
"name": "soma",
|
||||
"id": "soma"
|
||||
},
|
||||
{
|
||||
"name": "item",
|
||||
"id": "item"
|
||||
}
|
||||
]
|
||||
}
|
||||
181
app/public/examples/temperatura.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"blocks": {
|
||||
"languageVersion": 0,
|
||||
"blocks": [
|
||||
{
|
||||
"type": "variables_set",
|
||||
"id": "set_celsius",
|
||||
"x": 50,
|
||||
"y": 50,
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "celsius"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_number",
|
||||
"id": "temp_celsius",
|
||||
"fields": {
|
||||
"NUM": 25
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "variables_set",
|
||||
"id": "set_fahrenheit",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "fahrenheit"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"VALUE": {
|
||||
"block": {
|
||||
"type": "math_arithmetic",
|
||||
"id": "calc_fahrenheit",
|
||||
"fields": {
|
||||
"OP": "ADD"
|
||||
},
|
||||
"inputs": {
|
||||
"A": {
|
||||
"block": {
|
||||
"type": "math_arithmetic",
|
||||
"id": "multiply",
|
||||
"fields": {
|
||||
"OP": "MULTIPLY"
|
||||
},
|
||||
"inputs": {
|
||||
"A": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_celsius",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "celsius"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"B": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "factor",
|
||||
"fields": {
|
||||
"NUM": 1.8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"B": {
|
||||
"shadow": {
|
||||
"type": "math_number",
|
||||
"id": "add_32",
|
||||
"fields": {
|
||||
"NUM": 32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "text_print",
|
||||
"id": "print_celsius",
|
||||
"inputs": {
|
||||
"TEXT": {
|
||||
"block": {
|
||||
"type": "text_join",
|
||||
"id": "join_celsius",
|
||||
"extraState": {
|
||||
"itemCount": 2
|
||||
},
|
||||
"inputs": {
|
||||
"ADD0": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_celsius_print",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "celsius"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD1": {
|
||||
"block": {
|
||||
"type": "text",
|
||||
"id": "text_celsius",
|
||||
"fields": {
|
||||
"TEXT": "°C"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next": {
|
||||
"block": {
|
||||
"type": "text_print",
|
||||
"id": "print_fahrenheit",
|
||||
"inputs": {
|
||||
"TEXT": {
|
||||
"block": {
|
||||
"type": "text_join",
|
||||
"id": "join_fahrenheit",
|
||||
"extraState": {
|
||||
"itemCount": 2
|
||||
},
|
||||
"inputs": {
|
||||
"ADD0": {
|
||||
"block": {
|
||||
"type": "variables_get",
|
||||
"id": "get_fahrenheit_print",
|
||||
"fields": {
|
||||
"VAR": {
|
||||
"id": "fahrenheit"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD1": {
|
||||
"block": {
|
||||
"type": "text",
|
||||
"id": "text_fahrenheit",
|
||||
"fields": {
|
||||
"TEXT": "°F"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "celsius",
|
||||
"id": "celsius"
|
||||
},
|
||||
{
|
||||
"name": "fahrenheit",
|
||||
"id": "fahrenheit"
|
||||
}
|
||||
]
|
||||
}
|
||||
3
app/public/health.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"status": "UP"
|
||||
}
|
||||
BIN
app/public/images/atividades/programacao/aspirador-thumbnail.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
app/public/images/atividades/programacao/automato-thumbnail.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
app/public/images/atividades/programacao/cripto-thumbnail.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
app/public/images/atividades/programacao/molemash-thumbnail.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
app/public/images/atividades/programacao/ordenacao-thumbnail.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
app/public/images/atividades/programacao/puzzle-thumbnail.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
app/public/images/atividades/programacao/semaforo-thumbnail.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
app/public/images/atividades/programacao/turtle-thumbnail.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
app/public/img/logo.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
app/public/img/logo_192x192.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
app/public/img/logo_512x512.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/public/img/logo_decoda.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
app/public/img/logo_nav.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
21
app/public/manifest.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Decoda",
|
||||
"short_name": "Decoda",
|
||||
"description": "Aprenda programação brincando com blocos!",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#FE0002",
|
||||
"icons": [
|
||||
{
|
||||
"src": "img/logo_192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "img/logo_512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
78
app/public/offline.html
Normal file
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Decoda - Offline</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--brand-50: #fef7ee;
|
||||
--brand-100: #fdebd3;
|
||||
--brand-500: #fe0002;
|
||||
--brand-700: #c73e2b;
|
||||
--ink-900: #242527;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Lato", system-ui, -apple-system, Segoe UI, sans-serif;
|
||||
background: radial-gradient(circle at top, #ffffff 0%, var(--brand-50) 55%, var(--brand-100) 100%);
|
||||
color: var(--ink-900);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.card {
|
||||
max-width: 640px;
|
||||
width: min(92vw, 640px);
|
||||
margin: 24px;
|
||||
padding: 32px;
|
||||
border-radius: 20px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 18px 40px rgba(36, 37, 39, 0.14);
|
||||
border: 1px solid rgba(254, 0, 2, 0.08);
|
||||
}
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 28px;
|
||||
color: var(--ink-900);
|
||||
}
|
||||
p {
|
||||
margin: 0 0 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.tip {
|
||||
font-size: 14px;
|
||||
color: #5b5e63;
|
||||
}
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(254, 0, 2, 0.08);
|
||||
color: var(--brand-500);
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div>
|
||||
<img src="/img/logo_decoda.png" alt="Decoda" />
|
||||
</div>
|
||||
<h1>Não foi possível carregar a página</h1>
|
||||
<p>Não conseguimos carregar esta página neste momento.</p>
|
||||
<p class="tip">Conecte-se à internet para continuar ou volte para a tela inicial.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
14
app/public/puzzle.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2050 2050" id="Puzzle">
|
||||
<path fill="#fbbc05" d="M1084.6 461.6a146 146 0 0 1-92.4 184.6 144.5 144.5 0 0 1-28.5 6.4l61.9 185.7-198.9 66.2a143.8 143.8 0 0 0-5.6-69.7C795.7 758.3 713 717 636.5 742.4a146.1 146.1 0 0 0-55 243.8l-198.8 66.2-167.9-504.1a146.2 146.2 0 0 1 92.6-185l504.1-167.9 61.8 185.7a146.3 146.3 0 0 1 211.3 80.5Z" class="colorf8881b svgShape"></path>
|
||||
<path fill="#34a853" d="M1084.6 461.6a146 146 0 0 0-86.7-90.4 146.1 146.1 0 0 1-53.6 245.2 143.5 143.5 0 0 1-28.4 6.4l61.8 185.7-150.8 50.2a143.5 143.5 0 0 1-.2 45.8l198.9-66.2-61.9-185.7a144.5 144.5 0 0 0 28.5-6.4 146 146 0 0 0 92.4-184.6Z" class="colorf89a3e svgShape"></path>
|
||||
<path fill="#f0b713" d="M303.5 604.1a146.2 146.2 0 0 1 92.6-185l441-146.9-25.6-76.8-504.1 167.9a146.2 146.2 0 0 0-92.6 185l167.9 504.1 63-21Z" class="colorf08013 svgShape"></path>
|
||||
<path fill="#6799eb" d="M1842.7 1177h-205.8a146 146 0 1 1-278.9 60.3 144.7 144.7 0 0 1 13-60.3h-205.9V981.3a146 146 0 1 0 0-286.2V499.4h531.3a146.3 146.3 0 0 1 146.3 146.3Z" class="color67baeb svgShape"></path>
|
||||
<path fill="#4285f4" d="M1215.3 1012.8a186.5 186.5 0 0 0 36.1 3.6A181.6 181.6 0 0 0 1433 834.8c0-100.3-81.3-181.5-181.6-181.5a186.5 186.5 0 0 0-36.1 3.5V499.4h-50.2v195.7a145.9 145.9 0 1 1 0 286.2V1177h50.2Z" class="color46a1f8 svgShape"></path>
|
||||
<path fill="#f4c63f" d="M958.6 1515.8a146 146 0 0 0 145.9 146 144.3 144.3 0 0 0 60.6-13.2v206H633.8a146.3 146.3 0 0 1-146.3-146.3V1177h209.6a146 146 0 1 1 258.4 0h209.6v206a144.3 144.3 0 0 0-60.6-13.2 146 146 0 0 0-145.9 146Z" class="colorf4c23f svgShape"></path>
|
||||
<path fill="#fad464" d="M955.5 1177a146 146 0 0 0-257.7-137.3 146.1 146.1 0 0 1 206.3 192h209.5v138.4a143.1 143.1 0 0 1 51.5 12.9v-206Z" class="colorfad564 svgShape"></path>
|
||||
<path fill="#f4c63f" d="M704.5 1789.4a146.3 146.3 0 0 1-146.3-146.3V1177h-70.7v531.3a146.3 146.3 0 0 0 146.3 146.3h531.3v-65.2Z" class="colorf4a93f svgShape"></path>
|
||||
<path fill="#de3426" d="M1842.7 1177v531.3a146.3 146.3 0 0 1-146.3 146.3h-531.3v-206a144.3 144.3 0 0 1-60.6 13.2 146 146 0 0 1 0-292 144.3 144.3 0 0 1 60.6 13.2v-206H1371a146 146 0 1 0 278.9 60.3 146.3 146.3 0 0 0-13-60.3Z" class="colorde3226 svgShape"></path>
|
||||
<path fill="#b1261a" d="M1529.3 1381.1a150.9 150.9 0 0 0 25.4 2.2 146.1 146.1 0 0 0 132.9-206.3h-50.7a146.3 146.3 0 0 1 13 60.3c0 71.9-52.1 131.7-120.6 143.8zm-313.4 1.9v-206h-50.8v193.2a144.6 144.6 0 0 1 50.8 12.8zm-206.6 132.8a146 146 0 0 1 120.6-143.8 151.8 151.8 0 0 0-25.4-2.2 146 146 0 0 0 0 292 151.8 151.8 0 0 0 25.4-2.2c-68.5-12.1-120.6-71.9-120.6-143.8zm155.8 145.6v193.2h50.8v-206a144.6 144.6 0 0 1-50.8 12.8z" class="colorb11a31 svgShape"></path>
|
||||
<rect width="111.8" height="329.42" x="1730.9" y="723" fill="#83b0fb" rx="55.9" ry="55.9" class="color83d0fb svgShape"></rect>
|
||||
<rect width="111.8" height="277.54" x="1730.9" y="1438.9" fill="#ea4335" rx="55.9" ry="55.9" class="colorf44533 svgShape"></rect>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
48
app/scripts/test-cache.mjs
Normal file
@@ -0,0 +1,48 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
const distDir = path.resolve(process.cwd(), 'dist');
|
||||
const swCandidates = ['sw.js', 'service-worker.js'];
|
||||
|
||||
async function fileExists(filePath) {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const swPath = await (async () => {
|
||||
for (const name of swCandidates) {
|
||||
const fullPath = path.join(distDir, name);
|
||||
if (await fileExists(fullPath)) return fullPath;
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
|
||||
if (!swPath) {
|
||||
throw new Error('Service worker file not found in dist/.');
|
||||
}
|
||||
|
||||
const swContent = await fs.readFile(swPath, 'utf8');
|
||||
const required = [
|
||||
/offline\.html/,
|
||||
/atividades\/letramento\//,
|
||||
/letramento\.css/,
|
||||
/lucide\.js/,
|
||||
];
|
||||
|
||||
const missing = required.filter((regex) => !regex.test(swContent));
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Precache entries missing: ${missing.map(String).join(', ')}`);
|
||||
}
|
||||
|
||||
console.log('Cache test passed.');
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
5
app/serve.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"public": "dist",
|
||||
"cleanUrls": false,
|
||||
"rewrites": [{ "source": "!docs/**", "destination": "/index.html" }]
|
||||
}
|
||||
145
app/src/App.css
Normal file
@@ -0,0 +1,145 @@
|
||||
@import "./styles/globals.css";
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Floating animations for hero section */
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Game card hover effects */
|
||||
.game-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.game-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Category cards */
|
||||
.category-card {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.category-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Gradient text effects */
|
||||
.gradient-text {
|
||||
background: linear-gradient(45deg, #fe0002 0%, #f50c52 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Button animations */
|
||||
.btn-gradient {
|
||||
background: linear-gradient(45deg, #fe0002 0%, #f50c52 100%);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-gradient:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
/* Backdrop blur support */
|
||||
.backdrop-blur {
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(45deg, #fe0002 0%, #f50c52 100%);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(45deg, #fe0002 0%, #f50c52 100%);
|
||||
}
|
||||
|
||||
/* Enhanced shadow effects */
|
||||
.shadow-3xl {
|
||||
box-shadow: 0 35px 60px -12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
/* Custom carousel styles */
|
||||
.carousel-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.carousel-slide {
|
||||
transition: transform 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Pagination dots */
|
||||
.carousel-dot {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.carousel-dot:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.carousel-dot.active {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
min-height: 100vh;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
121
app/src/App.jsx
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* @fileoverview React component for App.jsx
|
||||
*
|
||||
* @module App
|
||||
*/
|
||||
|
||||
import { lazy, Suspense } from "react";
|
||||
import { HashRouter as Router, Routes, Route, useLocation } from "react-router-dom";
|
||||
import "./App.css";
|
||||
import HomePage from "./pages/HomePage/HomePage";
|
||||
import LabPython from "./pages/LabPython/LabPython";
|
||||
import ScrollToTop from "./components/ScrollToTop";
|
||||
|
||||
const Playground = lazy(() => import("./pages/Playground/Playground"));
|
||||
const About = lazy(() => import("./pages/About/About"));
|
||||
const Faq = lazy(() => import("./pages/Faq/Faq"));
|
||||
const Atividades = lazy(() => import("./pages/Atividades/Atividades"));
|
||||
const Educadores = lazy(() => import("./pages/Educadores/Educadores"));
|
||||
const Iniciativas = lazy(() => import("./pages/Iniciativas/Iniciativas"));
|
||||
const IniciativaDetalhe = lazy(() => import("./pages/Iniciativas/IniciativaDetalhe"));
|
||||
const PrimeirosPassos = lazy(() => import("./pages/PrimeirosPassos/PrimeirosPassos"));
|
||||
const CategoriaLetramentoView = lazy(() => import("./pages/PrimeirosPassos/CategoriaLetramentoView"));
|
||||
|
||||
//Atividades
|
||||
const AspiradorGame = lazy(() => import("./atividades/programacao/aspirador/AspiradorGame"));
|
||||
const AutomatoGame = lazy(() => import("./atividades/programacao/automato/AutomatoGame"));
|
||||
const SemaforoGame = lazy(() => import("./atividades/programacao/semaforo/SemaforoGame"));
|
||||
const MoleMashGame = lazy(() => import("./atividades/programacao/mole-mash/MoleMash"));
|
||||
const OrdenacaoGame = lazy(() => import("./atividades/programacao/ordenacao/OrdenacaoGame"));
|
||||
const PuzzleGame = lazy(() => import("./atividades/programacao/puzzle/PuzzleGame"));
|
||||
const TurtleGame = lazy(() => import("./atividades/programacao/turtle/TurtleGame"));
|
||||
const CriptoGame = lazy(() => import("./atividades/programacao/cripto/CriptoGame"));
|
||||
|
||||
const LoadingFallback = () => (
|
||||
<div
|
||||
className="primary-gradient text-white"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "100vh",
|
||||
fontSize: "1.5rem",
|
||||
fontWeight: "600",
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div
|
||||
style={{
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
border: "4px solid rgba(255,255,255,0.3)",
|
||||
borderTop: "4px solid white",
|
||||
borderRadius: "50%",
|
||||
animation: "spin 1s linear infinite",
|
||||
margin: "0 auto 20px",
|
||||
}}
|
||||
></div>
|
||||
Carregando...
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Separated so we can call useLocation (requires being inside Router)
|
||||
function AppRoutes() {
|
||||
const location = useLocation();
|
||||
// When navigating to a letramento category, the caller passes
|
||||
// { state: { backgroundLocation: location } } so the previous page
|
||||
// keeps rendering behind the modal overlay.
|
||||
const backgroundLocation = location.state?.backgroundLocation;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollToTop />
|
||||
{/* Main routes — rendered at the background location when a modal is open */}
|
||||
<Routes location={backgroundLocation ?? location}>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/playground" element={<Playground />} />
|
||||
<Route path="/laboratorio" element={<Playground />} />
|
||||
<Route path="/laboratorio-python" element={<LabPython />} />
|
||||
<Route path="/sobre" element={<About />} />
|
||||
<Route path="/faq" element={<Faq />} />
|
||||
<Route path="/atividades" element={<Atividades />} />
|
||||
<Route path="/educadores" element={<Educadores />} />
|
||||
<Route path="/iniciativas" element={<Iniciativas />} />
|
||||
<Route path="/iniciativas/:id" element={<IniciativaDetalhe />} />
|
||||
<Route path="/primeiros-passos" element={<PrimeirosPassos />} />
|
||||
{/* Fallback: direct URL access without backgroundLocation */}
|
||||
<Route path="/primeiros-passos/:categoria" element={<CategoriaLetramentoView />} />
|
||||
<Route path="/atividades/programacao/aspirador" element={<AspiradorGame />} />
|
||||
<Route path="/atividades/programacao/automato" element={<AutomatoGame />} />
|
||||
<Route path="/atividades/programacao/cripto" element={<CriptoGame />} />
|
||||
<Route path="/atividades/programacao/ordenacao" element={<OrdenacaoGame />} />
|
||||
<Route path="/atividades/programacao/puzzle" element={<PuzzleGame />} />
|
||||
<Route path="/atividades/programacao/semaforo" element={<SemaforoGame />} />
|
||||
<Route path="/atividades/programacao/molemash" element={<MoleMashGame />} />
|
||||
<Route path="/atividades/programacao/turtle" element={<TurtleGame />} />
|
||||
</Routes>
|
||||
|
||||
{/* Modal overlay routes — rendered on top of the background page */}
|
||||
{backgroundLocation && (
|
||||
<Routes>
|
||||
<Route path="/primeiros-passos/:categoria" element={<CategoriaLetramentoView />} />
|
||||
</Routes>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Router>
|
||||
<Suspense fallback={<LoadingFallback />}>
|
||||
<AppRoutes />
|
||||
</Suspense>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
// Note: many imports are used only via lazy() or in route elements.
|
||||
// If ESLint still reports unused vars here, we prefer keeping changes minimal
|
||||
// and addressing remaining warnings in targeted files.
|
||||
BIN
app/src/assets/baner_quemsomos.png
Normal file
|
After Width: | Height: | Size: 455 KiB |
BIN
app/src/assets/banner_quemsomos_mobile.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
app/src/assets/car.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
app/src/assets/fail.mp3
Normal file
BIN
app/src/assets/fonts/SF Slapstick Comic Shaded.ttf
Normal file
BIN
app/src/assets/game_loop.mp3
Normal file
3
app/src/assets/icon_cabeca.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="37" viewBox="0 0 32 37" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.0312 36.7344C15.3021 36.7344 13.1094 36.2708 11.4531 35.3438C9.79688 34.4271 8.88021 33.1198 8.70312 31.4219V31.3125L7.92188 31.375C6.36979 31.4271 5.125 31.0365 4.1875 30.2031C3.26042 29.3698 2.79688 28.1562 2.79688 26.5625V24.7344C1.84896 24.5781 1.14583 24.2188 0.6875 23.6562C0.229167 23.0938 0 22.4427 0 21.7031C0 20.8802 0.25 20.026 0.75 19.1406L2.84375 15.4062C2.82292 15.2396 2.80729 15.0677 2.79688 14.8906C2.79688 14.7031 2.79688 14.5104 2.79688 14.3125C2.79688 12.1667 3.13542 10.2188 3.8125 8.46875C4.48958 6.71875 5.45312 5.21354 6.70312 3.95312C7.95312 2.68229 9.44792 1.70833 11.1875 1.03125C12.9271 0.34375 14.8594 0 16.9844 0C19.1094 0 21.0417 0.348958 22.7812 1.04688C24.5312 1.74479 26.0312 2.73958 27.2812 4.03125C28.5417 5.3125 29.5104 6.85417 30.1875 8.65625C30.875 10.4479 31.2188 12.4375 31.2188 14.625C31.2188 16.4583 30.8958 18.1771 30.25 19.7812C29.6042 21.3854 28.7031 22.724 27.5469 23.7969V30.7969C27.5469 32.6719 26.6927 34.125 24.9844 35.1562C23.2865 36.1979 20.9688 36.724 18.0312 36.7344ZM11.4844 16.4688C12.224 16.4688 12.8281 16.2552 13.2969 15.8281C13.776 15.401 14.0156 14.8594 14.0156 14.2031C14.0156 13.7552 13.8594 13.3802 13.5469 13.0781C13.2969 12.849 13.1719 12.5938 13.1719 12.3125C13.1719 12.0833 13.2552 11.8906 13.4219 11.7344C13.5885 11.5781 13.7917 11.5 14.0312 11.5C14.2917 11.5 14.5312 11.5938 14.75 11.7812C14.8229 11.8438 14.8906 11.9167 14.9531 12C15.0156 12.0729 15.0729 12.151 15.125 12.2344C15.4896 12.1406 15.75 11.9688 15.9062 11.7188C16.0625 11.4583 16.1406 11.1302 16.1406 10.7344C16.1406 10.4844 16.224 10.2708 16.3906 10.0938C16.5573 9.91667 16.7604 9.82812 17 9.82812C17.2396 9.82812 17.4479 9.91667 17.625 10.0938C17.8125 10.2708 17.9062 10.4844 17.9062 10.7344C17.9062 12.3698 17.1667 13.4167 15.6875 13.875C15.7083 13.9792 15.7188 14.0885 15.7188 14.2031C15.7188 14.3073 15.7188 14.4167 15.7188 14.5312C15.6979 15.0938 15.5625 15.6094 15.3125 16.0781C15.0729 16.5365 14.7396 16.9219 14.3125 17.2344C15.0104 17.5573 15.7865 17.7188 16.6406 17.7188C16.7552 17.7188 16.8854 17.7135 17.0312 17.7031C17.1875 17.6927 17.3594 17.6719 17.5469 17.6406C17.5156 17.4635 17.5 17.2656 17.5 17.0469C17.5 16.1719 17.6927 15.4375 18.0781 14.8438C18.474 14.25 18.9635 13.7344 19.5469 13.2969C20.1302 12.8594 20.7083 12.4375 21.2812 12.0312C21.8646 11.625 22.349 11.1771 22.7344 10.6875C23.1302 10.1875 23.3281 9.58333 23.3281 8.875C23.3281 8.30208 23.1823 7.78125 22.8906 7.3125C22.599 6.84375 22.2083 6.46875 21.7188 6.1875C21.2292 5.90625 20.7031 5.77604 20.1406 5.79688C19.974 5.79688 19.8438 5.79688 19.75 5.79688C19.6562 5.79688 19.5677 5.80208 19.4844 5.8125C19.1719 5.5 18.8281 5.25521 18.4531 5.07812C18.0781 4.89062 17.6875 4.79688 17.2812 4.79688C16.625 4.79688 16.0781 4.98958 15.6406 5.375C15.2135 5.75 14.9948 6.22917 14.9844 6.8125C14.9844 7.07292 14.901 7.28646 14.7344 7.45312C14.5781 7.60938 14.375 7.6875 14.125 7.6875C13.875 7.6875 13.6667 7.59896 13.5 7.42188C13.3333 7.24479 13.25 7.02083 13.25 6.75C13.2604 6.54167 13.2812 6.34896 13.3125 6.17188C13.3542 5.98438 13.401 5.8125 13.4531 5.65625C13.276 5.63542 13.0833 5.625 12.875 5.625C11.9271 5.625 11.1875 5.86458 10.6562 6.34375C10.1354 6.82292 9.875 7.33854 9.875 7.89062C9.875 8.22396 9.97396 8.50521 10.1719 8.73438C10.3802 8.96354 10.651 9.07812 10.9844 9.07812C11.2344 9.07812 11.4427 9.16667 11.6094 9.34375C11.7865 9.51042 11.875 9.71354 11.875 9.95312C11.875 10.1927 11.7865 10.401 11.6094 10.5781C11.4427 10.7552 11.2344 10.8438 10.9844 10.8438C10.474 10.8438 10.0156 10.75 9.60938 10.5625C9.20312 10.375 8.88021 10.1042 8.64062 9.75C8.08854 10.5417 7.8125 11.4427 7.8125 12.4531C7.8125 13.599 8.15625 14.5573 8.84375 15.3281C9.53125 16.0885 10.4115 16.4688 11.4844 16.4688ZM22.9062 20.5938C23.7396 20.5938 24.4219 20.2031 24.9531 19.4219C25.4948 18.6302 25.7656 17.5885 25.7656 16.2969C25.7656 16.224 25.7656 16.151 25.7656 16.0781C25.7656 16.0052 25.7656 15.9323 25.7656 15.8594C25.5156 15.9323 25.2396 15.9896 24.9375 16.0312C24.6354 16.0729 24.3229 16.0781 24 16.0469C23.75 16.0365 23.5417 15.9479 23.375 15.7812C23.2188 15.6146 23.1406 15.4115 23.1406 15.1719C23.1406 14.9219 23.2292 14.7135 23.4062 14.5469C23.5833 14.3698 23.7917 14.2917 24.0312 14.3125C24.7708 14.375 25.3646 14.2083 25.8125 13.8125C26.2604 13.4062 26.4844 12.8698 26.4844 12.2031C26.4844 11.5885 26.349 11.0417 26.0781 10.5625C25.8177 10.0833 25.4531 9.67708 24.9844 9.34375C24.8594 10.1562 24.5781 10.8542 24.1406 11.4375C23.7031 12.0208 23.1979 12.5365 22.625 12.9844C22.0625 13.4219 21.5104 13.8385 20.9688 14.2344C20.4375 14.6302 19.9948 15.0469 19.6406 15.4844C19.2865 15.9115 19.1094 16.4115 19.1094 16.9844C19.1094 17.3802 19.224 17.7083 19.4531 17.9688C19.6927 18.2292 20.026 18.3594 20.4531 18.3594H20.5781C20.7344 19.0156 21.0208 19.5521 21.4375 19.9688C21.8646 20.3854 22.3542 20.5938 22.9062 20.5938Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
3
app/src/assets/icon_circulo.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="39" height="32" viewBox="0 0 39 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.65625 15.7188L4.73438 10.125C5.07812 9.66667 5.48958 9.4375 5.96875 9.4375C6.44792 9.42708 6.85417 9.65625 7.1875 10.125L11.2656 15.7344C11.7031 16.3073 11.8333 16.8333 11.6562 17.3125C11.4792 17.7917 11.0573 18.0312 10.3906 18.0312H8.125C8.36458 19.3021 8.8125 20.4948 9.46875 21.6094C10.1354 22.7135 10.9688 23.6875 11.9688 24.5312C12.9688 25.375 14.099 26.0365 15.3594 26.5156C16.6198 26.9948 17.9688 27.2344 19.4062 27.2344C20.8542 27.2344 22.2188 26.974 23.5 26.4531C24.7812 25.9323 25.9115 25.224 26.8906 24.3281C27.5469 23.8281 28.1667 23.625 28.75 23.7188C29.3333 23.8125 29.7917 24.0833 30.125 24.5312C30.4375 24.9479 30.5677 25.4479 30.5156 26.0312C30.4635 26.6042 30.1615 27.1146 29.6094 27.5625C28.2969 28.7604 26.7656 29.7188 25.0156 30.4375C23.2656 31.1667 21.3958 31.5312 19.4062 31.5312C17.3854 31.5312 15.4844 31.1771 13.7031 30.4688C11.9323 29.7708 10.3438 28.8021 8.9375 27.5625C7.54167 26.3229 6.39062 24.8906 5.48438 23.2656C4.57812 21.6302 3.99479 19.8854 3.73438 18.0312H1.51562C0.838542 18.0312 0.416667 17.7917 0.25 17.3125C0.0833333 16.8333 0.21875 16.3021 0.65625 15.7188ZM9.20312 3.95312C10.5156 2.75521 12.0469 1.79688 13.7969 1.07812C15.5573 0.359375 17.4271 0 19.4062 0C21.4167 0 23.3125 0.354167 25.0938 1.0625C26.875 1.76042 28.4688 2.72917 29.875 3.96875C31.2812 5.20833 32.4323 6.64583 33.3281 8.28125C34.224 9.90625 34.8021 11.6458 35.0625 13.5H37.2344C37.9115 13.5 38.3333 13.7396 38.5 14.2188C38.6771 14.6979 38.5521 15.224 38.125 15.7969L34.0312 21.4062C33.6875 21.8542 33.276 22.0833 32.7969 22.0938C32.3177 22.0938 31.9062 21.8646 31.5625 21.4062L27.4844 15.7969C27.0573 15.2135 26.9323 14.6875 27.1094 14.2188C27.2865 13.7396 27.7031 13.5 28.3594 13.5H30.6875C30.4479 12.2292 29.9948 11.0365 29.3281 9.92188C28.6719 8.80729 27.8385 7.82812 26.8281 6.98438C25.8281 6.14062 24.6979 5.48438 23.4375 5.01562C22.1875 4.53646 20.8438 4.29688 19.4062 4.29688C17.9583 4.29688 16.5938 4.55729 15.3125 5.07812C14.0417 5.59896 12.9115 6.30208 11.9219 7.1875C11.2656 7.69792 10.6458 7.90625 10.0625 7.8125C9.47917 7.70833 9.02083 7.43229 8.6875 6.98438C8.375 6.55729 8.24479 6.05729 8.29688 5.48438C8.34896 4.91146 8.65104 4.40104 9.20312 3.95312Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
3
app/src/assets/icon_elo.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.6562 10.1875C19.2812 10.3333 19.8594 10.5573 20.3906 10.8594C20.9323 11.151 21.3802 11.4688 21.7344 11.8125C23.026 13.0938 23.8854 14.5156 24.3125 16.0781C24.7396 17.6302 24.7396 19.1823 24.3125 20.7344C23.8854 22.2865 23.0312 23.7031 21.75 24.9844L16.3906 30.3281C15.1198 31.6094 13.7083 32.4635 12.1562 32.8906C10.6042 33.3281 9.04688 33.3281 7.48438 32.8906C5.93229 32.4635 4.51042 31.6042 3.21875 30.3125C1.9375 29.0312 1.07812 27.6146 0.640625 26.0625C0.213542 24.5 0.213542 22.9427 0.640625 21.3906C1.06771 19.8385 1.92188 18.4219 3.20312 17.1406L7.1875 13.1875C6.9375 13.9896 6.86458 14.8333 6.96875 15.7188C7.07292 16.5938 7.28125 17.3854 7.59375 18.0938L6 19.6875C5.22917 20.4583 4.71354 21.3073 4.45312 22.2344C4.20312 23.151 4.20833 24.0677 4.46875 24.9844C4.72917 25.901 5.25 26.75 6.03125 27.5312C6.79167 28.3021 7.63021 28.8125 8.54688 29.0625C9.47396 29.3229 10.3958 29.3281 11.3125 29.0781C12.2292 28.8281 13.0729 28.3177 13.8438 27.5469L18.9688 22.4375C19.7292 21.6667 20.2344 20.8229 20.4844 19.9062C20.7448 18.9792 20.7448 18.0573 20.4844 17.1406C20.2344 16.2135 19.7188 15.3698 18.9375 14.6094C18.5625 14.2344 18.0885 13.9167 17.5156 13.6562C16.9427 13.3958 16.3333 13.224 15.6875 13.1406L18.6562 10.1875ZM14.9219 23.3594C14.2865 23.2135 13.6979 22.9948 13.1562 22.7031C12.625 22.401 12.1875 22.0781 11.8438 21.7344C10.5521 20.4531 9.6875 19.0365 9.25 17.4844C8.82292 15.9219 8.82292 14.3646 9.25 12.8125C9.67708 11.2604 10.5312 9.84375 11.8125 8.5625L17.1719 3.21875C18.4531 1.92708 19.8698 1.06771 21.4219 0.640625C22.974 0.213542 24.526 0.21875 26.0781 0.65625C27.6302 1.08333 29.0521 1.94271 30.3438 3.23438C31.6354 4.51562 32.4948 5.9375 32.9219 7.5C33.3594 9.05208 33.3646 10.6042 32.9375 12.1562C32.5104 13.7083 31.651 15.125 30.3594 16.4062L26.375 20.3594C26.6354 19.5573 26.7083 18.7188 26.5938 17.8438C26.4896 16.9583 26.2865 16.1615 25.9844 15.4531L27.5625 13.8438C28.3333 13.0833 28.8438 12.2448 29.0938 11.3281C29.3542 10.401 29.3542 9.47917 29.0938 8.5625C28.8438 7.63542 28.3281 6.78646 27.5469 6.01562C26.776 5.24479 25.9271 4.73438 25 4.48438C24.0833 4.22396 23.1667 4.21875 22.25 4.46875C21.3333 4.71875 20.4896 5.22917 19.7188 6L14.6094 11.1094C13.8385 11.8802 13.3229 12.724 13.0625 13.6406C12.8125 14.5573 12.8177 15.4792 13.0781 16.4062C13.3385 17.3229 13.8542 18.1667 14.625 18.9375C15 19.3125 15.474 19.6302 16.0469 19.8906C16.6198 20.1406 17.2344 20.3073 17.8906 20.3906L14.9219 23.3594Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
3
app/src/assets/icon_livro.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="37" height="29" viewBox="0 0 37 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.82812 0C10.526 0 12.0885 0.322917 13.5156 0.96875C14.9427 1.60417 16.0052 2.46354 16.7031 3.54688C16.8698 3.79688 16.974 4 17.0156 4.15625C17.0677 4.30208 17.0938 4.48438 17.0938 4.70312V27.4688C17.0938 27.6875 17.0208 27.8646 16.875 28C16.7292 28.1354 16.5521 28.2031 16.3438 28.2031C16.0729 28.2031 15.8229 28.1094 15.5938 27.9219C14.7917 27.2656 13.8958 26.7656 12.9062 26.4219C11.9271 26.0885 10.9167 25.9219 9.875 25.9219C8.85417 25.9219 7.84896 26.0677 6.85938 26.3594C5.86979 26.6615 4.95312 27.099 4.10938 27.6719C3.68229 27.9219 3.32812 28.1042 3.04688 28.2188C2.77604 28.3438 2.44792 28.4062 2.0625 28.4062C1.41667 28.4062 0.911458 28.2292 0.546875 27.875C0.182292 27.5312 0 27.026 0 26.3594V5.85938C0 5.40104 0.0260417 5.05729 0.078125 4.82812C0.140625 4.58854 0.255208 4.32812 0.421875 4.04688C0.890625 3.24479 1.5625 2.54167 2.4375 1.9375C3.3125 1.32292 4.30729 0.848958 5.42188 0.515625C6.53646 0.171875 7.67188 0 8.82812 0ZM27.8594 0C29.0052 0 30.1302 0.171875 31.2344 0.515625C32.349 0.848958 33.3438 1.32292 34.2188 1.9375C35.1042 2.54167 35.7812 3.24479 36.25 4.04688C36.4167 4.32812 36.526 4.58854 36.5781 4.82812C36.6406 5.05729 36.6719 5.40104 36.6719 5.85938V26.3594C36.6719 27.026 36.4896 27.5312 36.125 27.875C35.7604 28.2292 35.2552 28.4062 34.6094 28.4062C34.224 28.4062 33.8906 28.3438 33.6094 28.2188C33.3281 28.1042 32.9792 27.9219 32.5625 27.6719C31.7188 27.099 30.8021 26.6615 29.8125 26.3594C28.8229 26.0677 27.8177 25.9219 26.7969 25.9219C25.7448 25.9219 24.7292 26.0885 23.75 26.4219C22.7708 26.7656 21.875 27.2656 21.0625 27.9219C20.8438 28.1094 20.599 28.2031 20.3281 28.2031C20.1198 28.2031 19.9427 28.1354 19.7969 28C19.651 27.8646 19.5781 27.6875 19.5781 27.4688V4.70312C19.5781 4.48438 19.599 4.30208 19.6406 4.15625C19.6927 4 19.7969 3.79688 19.9531 3.54688C20.6615 2.46354 21.7292 1.60417 23.1562 0.96875C24.5833 0.322917 26.151 0 27.8594 0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
3
app/src/assets/icon_mao.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="31" height="32" viewBox="0 0 31 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.4375 27.5625L0.609375 16.7344C0.203125 16.3385 0 15.875 0 15.3438C0 14.8125 0.203125 14.3385 0.609375 13.9219C1.00521 13.526 1.46875 13.3333 2 13.3438C2.54167 13.3438 3.01562 13.5469 3.42188 13.9531L10.1094 20.6406C10.1615 20.4635 10.2188 20.2812 10.2812 20.0938C10.3438 19.9062 10.4115 19.7188 10.4844 19.5312L0.875 9.9375C0.479167 9.53125 0.28125 9.0625 0.28125 8.53125C0.28125 7.98958 0.479167 7.51562 0.875 7.10938C1.27083 6.71354 1.73438 6.52083 2.26562 6.53125C2.80729 6.53125 3.28125 6.72917 3.6875 7.125L12.5625 16C12.6667 15.8542 12.7708 15.7135 12.875 15.5781C12.9896 15.4323 13.1094 15.2865 13.2344 15.1406L3.60938 5.53125C3.20312 5.11458 3 4.64583 3 4.125C3 3.59375 3.20312 3.125 3.60938 2.71875C4.01562 2.32292 4.48438 2.125 5.01562 2.125C5.54688 2.125 6.01042 2.32292 6.40625 2.71875L16.1406 12.4531C16.2969 12.349 16.4531 12.2552 16.6094 12.1719C16.776 12.0885 16.9375 12.0104 17.0938 11.9375L8.57812 3.40625C8.17188 3.01042 7.96875 2.54688 7.96875 2.01562C7.96875 1.47396 8.17188 1.00521 8.57812 0.609375C8.97396 0.203125 9.4375 0 9.96875 0C10.5 0 10.974 0.203125 11.3906 0.609375L22.6406 11.8594C22.776 12.0052 22.8906 12.0781 22.9844 12.0781C23.0885 12.0781 23.1771 12.0417 23.25 11.9688C23.3125 11.9062 23.349 11.8229 23.3594 11.7188C23.3698 11.6146 23.3385 11.4688 23.2656 11.2812L21.4219 6.46875C21.1927 5.85417 21.1302 5.29688 21.2344 4.79688C21.3385 4.28646 21.6354 3.86979 22.125 3.54688C22.5521 3.25521 22.9948 3.16146 23.4531 3.26562C23.9219 3.35938 24.3177 3.63542 24.6406 4.09375C24.8281 4.35417 25.0573 4.73958 25.3281 5.25C25.6094 5.75 25.9062 6.30208 26.2188 6.90625C26.5312 7.51042 26.8281 8.08854 27.1094 8.64062C27.3594 9.09896 27.6562 9.67708 28 10.375C28.3542 11.0729 28.7083 11.8333 29.0625 12.6562C29.4167 13.4792 29.7292 14.3281 30 15.2031C30.2708 16.0677 30.4531 16.9062 30.5469 17.7188C30.849 19.5729 30.7344 21.2969 30.2031 22.8906C29.6719 24.4844 28.6875 26.0052 27.25 27.4531C26.125 28.5781 24.901 29.4427 23.5781 30.0469C22.2552 30.651 20.8906 30.974 19.4844 31.0156C18.0885 31.0677 16.7031 30.8073 15.3281 30.2344C13.9531 29.6719 12.6562 28.7812 11.4375 27.5625Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
3
app/src/assets/icon_pasta.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="36" height="29" viewBox="0 0 36 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.39062 28.5938C3.67188 28.5938 2.34375 28.125 1.40625 27.1875C0.46875 26.2604 0 24.9375 0 23.2188V9.48438C0 7.76562 0.46875 6.44271 1.40625 5.51562C2.34375 4.58854 3.67188 4.125 5.39062 4.125H6.89062V2.5625C6.89062 1.76042 7.10938 1.13542 7.54688 0.6875C7.99479 0.229167 8.61458 0 9.40625 0H12.9531C13.7656 0 14.3906 0.229167 14.8281 0.6875C15.2656 1.13542 15.4844 1.76042 15.4844 2.5625V4.125H19.9219V2.5625C19.9219 1.76042 20.1458 1.13542 20.5938 0.6875C21.0417 0.229167 21.6615 0 22.4531 0H26C26.7917 0 27.4115 0.229167 27.8594 0.6875C28.3073 1.13542 28.5312 1.76042 28.5312 2.5625V4.125H30.0156C31.7552 4.125 33.0885 4.58854 34.0156 5.51562C34.9531 6.44271 35.4219 7.76562 35.4219 9.48438V23.2188C35.4219 24.9375 34.9531 26.2604 34.0156 27.1875C33.0885 28.125 31.7552 28.5938 30.0156 28.5938H5.39062Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 935 B |
BIN
app/src/assets/iniciativas-banner-mobile.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
18
app/src/assets/logo_decoda.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="180" height="83" viewBox="0 0 180 83" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.611 50.5987C14.0587 50.5987 13.611 50.151 13.611 49.5987V10.4693C13.611 9.91704 14.0587 9.46933 14.611 9.46933H23.0964C26.5753 9.46933 29.0945 10.2148 30.654 11.7057C32.2135 13.1967 33.0189 15.6559 33.0703 19.0833C33.1217 21.6882 33.156 24.1645 33.1731 26.5123C33.1903 28.843 33.1903 31.1822 33.1731 33.53C33.156 35.8607 33.1217 38.3285 33.0703 40.9333C33.0189 44.3779 32.2135 46.8543 30.654 48.3623C29.0945 49.8533 26.5667 50.5987 23.0707 50.5987H14.611ZM20.2688 43.7378C20.2688 44.2901 20.7165 44.7378 21.2688 44.7378H23.0707C24.1846 44.7378 25.0158 44.4807 25.5642 43.9666C26.1297 43.4525 26.4211 42.707 26.4382 41.7302C26.4896 39.7594 26.5239 37.8058 26.541 35.8693C26.5753 33.9156 26.5924 31.962 26.5924 30.0083C26.5924 28.0547 26.5753 26.101 26.541 24.1474C26.5239 22.1937 26.4896 20.2487 26.4382 18.3122C26.4211 17.3353 26.1297 16.5984 25.5642 16.1014C25.0158 15.5873 24.1932 15.3303 23.0964 15.3303H21.2688C20.7165 15.3303 20.2688 15.778 20.2688 16.3303V43.7378Z" fill="#F90527"/>
|
||||
<path d="M38.3086 50.5987C37.7563 50.5987 37.3086 50.151 37.3086 49.5987V10.4693C37.3086 9.91704 37.7563 9.46933 38.3086 9.46933H72.1927C72.745 9.46933 73.1927 9.91704 73.1927 10.4693V14.3303C73.1927 14.8826 72.745 15.3303 72.1927 15.3303H44.9664C44.4141 15.3303 43.9664 15.778 43.9664 16.3303V25.7951C43.9664 26.3474 44.4141 26.7951 44.9664 26.7951H71.6786C72.2309 26.7951 72.6786 27.2428 72.6786 27.7951V31.656C72.6786 32.2083 72.2309 32.656 71.6786 32.656H44.9664C44.4141 32.656 43.9664 33.1038 43.9664 33.656V43.7378C43.9664 44.2901 44.4141 44.7378 44.9664 44.7378H72.1927C72.745 44.7378 73.1927 45.1855 73.1927 45.7378V49.5987C73.1927 50.151 72.745 50.5987 72.1927 50.5987H38.3086Z" fill="#F90527"/>
|
||||
<path d="M85.9626 51.0872C82.4666 51.0872 79.9732 50.3588 78.4822 48.9022C77.0084 47.4455 76.2372 45.0291 76.1687 41.6531C76.1344 39.9736 76.1087 38.14 76.0916 36.152C76.0744 34.1641 76.0659 32.1248 76.0659 30.034C76.0659 27.9433 76.0744 25.904 76.0916 23.916C76.1087 21.911 76.1344 20.0602 76.1687 18.3636C76.2201 15.4502 77.0084 13.1624 78.5336 11.5001C80.076 9.83778 82.5523 9.00662 85.9626 9.00662C89.3215 9.00662 91.755 9.73496 93.2631 11.1916C94.7712 12.6311 95.5681 15.0304 95.6538 18.3893C95.6709 18.9891 95.6795 19.6574 95.6795 20.3943C95.6795 21.1312 95.6709 21.8767 95.6538 22.6307C95.6455 22.9848 95.6373 23.321 95.6291 23.6395C95.6151 24.1802 95.1722 24.6101 94.6313 24.6101H89.9949C89.4331 24.6101 88.9816 24.1474 88.9939 23.5857C89.0031 23.1638 89.0124 22.717 89.0216 22.2452C89.0388 21.354 89.0473 20.4629 89.0473 19.5717C89.0473 18.6806 89.0388 17.8837 89.0216 17.1811C89.0045 16.2728 88.7303 15.5873 88.199 15.1246C87.6849 14.6619 86.9395 14.4306 85.9626 14.4306C84.9515 14.4306 84.1804 14.6619 83.6491 15.1246C83.1178 15.5873 82.8351 16.2728 82.8008 17.1811C82.7323 19.169 82.6808 21.2512 82.6466 23.4276C82.6294 25.6041 82.6209 27.8062 82.6209 30.034C82.6209 32.2619 82.6294 34.464 82.6466 36.6404C82.6808 38.7997 82.7323 40.8733 82.8008 42.8613C82.8351 43.7867 83.1178 44.4893 83.6491 44.9692C84.1804 45.4319 84.9515 45.6632 85.9626 45.6632C87.0251 45.6632 87.8306 45.4319 88.379 44.9692C88.9274 44.4893 89.2101 43.7867 89.2273 42.8613C89.2444 42.1586 89.253 41.3532 89.253 40.4449C89.2701 39.5366 89.2701 38.6284 89.253 37.7201C89.253 37.2329 89.2505 36.7777 89.2456 36.3545C89.239 35.7925 89.6905 35.3294 90.2526 35.3294H94.8939C95.4325 35.3294 95.8749 35.756 95.8871 36.2945C95.9029 36.9912 95.9108 37.7235 95.9108 38.4913C95.9108 39.6052 95.8937 40.6591 95.8594 41.6531C95.7737 45.0291 94.9597 47.4455 93.4173 48.9022C91.8921 50.3588 89.4072 51.0872 85.9626 51.0872Z" fill="#201D1E"/>
|
||||
<path d="M109.41 51.0615C105.983 51.0615 103.507 50.3417 101.981 48.9022C100.456 47.4455 99.6593 45.0291 99.5908 41.6531C99.5565 39.7851 99.5222 37.8743 99.4879 35.9207C99.4708 33.967 99.4622 32.0048 99.4622 30.034C99.4622 28.0461 99.4708 26.0753 99.4879 24.1217C99.5222 22.168 99.5565 20.2487 99.5908 18.3636C99.6593 15.0047 100.456 12.6054 101.981 11.1659C103.507 9.72639 105.983 9.00662 109.41 9.00662C112.855 9.00662 115.323 9.72639 116.814 11.1659C118.305 12.6054 119.102 15.0047 119.204 18.3636C119.256 20.2658 119.299 22.1937 119.333 24.1474C119.367 26.101 119.384 28.0718 119.384 30.0597C119.384 32.0305 119.367 33.9927 119.333 35.9464C119.299 37.8829 119.256 39.7851 119.204 41.6531C119.102 45.0291 118.305 47.4455 116.814 48.9022C115.323 50.3417 112.855 51.0615 109.41 51.0615ZM109.41 45.6375C110.473 45.6375 111.261 45.4062 111.775 44.9435C112.289 44.4807 112.555 43.7867 112.572 42.8613C112.641 40.9248 112.692 38.8683 112.726 36.6919C112.761 34.4983 112.778 32.2704 112.778 30.0083C112.778 27.7291 112.761 25.5012 112.726 23.3248C112.692 21.1484 112.641 19.0919 112.572 17.1554C112.555 16.2471 112.281 15.5702 111.75 15.1246C111.218 14.6619 110.439 14.4306 109.41 14.4306C108.382 14.4306 107.602 14.6619 107.071 15.1246C106.54 15.5702 106.257 16.2471 106.223 17.1554C106.154 19.0919 106.094 21.1484 106.043 23.3248C106.009 25.5012 105.992 27.7291 105.992 30.0083C105.992 32.2704 106.009 34.4983 106.043 36.6919C106.094 38.8683 106.154 40.9248 106.223 42.8613C106.257 43.7867 106.54 44.4807 107.071 44.9435C107.602 45.4062 108.382 45.6375 109.41 45.6375Z" fill="#201D1E"/>
|
||||
<path d="M124.493 50.5987C123.941 50.5987 123.493 50.151 123.493 49.5987V10.4693C123.493 9.91704 123.941 9.46933 124.493 9.46933H132.978C136.457 9.46933 138.976 10.2148 140.536 11.7057C142.095 13.1967 142.901 15.6559 142.952 19.0833C143.004 21.6882 143.038 24.1645 143.055 26.5123C143.072 28.843 143.072 31.1822 143.055 33.53C143.038 35.8607 143.004 38.3285 142.952 40.9333C142.901 44.3779 142.095 46.8543 140.536 48.3623C138.976 49.8533 136.449 50.5987 132.953 50.5987H124.493ZM130.151 43.7378C130.151 44.2901 130.598 44.7378 131.151 44.7378H132.953C134.067 44.7378 134.898 44.4807 135.446 43.9666C136.012 43.4525 136.303 42.707 136.32 41.7302C136.371 39.7594 136.406 37.8058 136.423 35.8693C136.457 33.9156 136.474 31.962 136.474 30.0083C136.474 28.0547 136.457 26.101 136.423 24.1474C136.406 22.1937 136.371 20.2487 136.32 18.3122C136.303 17.3353 136.012 16.5984 135.446 16.1014C134.898 15.5873 134.075 15.3303 132.978 15.3303H131.151C130.598 15.3303 130.151 15.778 130.151 16.3303V43.7378Z" fill="#201D1E"/>
|
||||
<path d="M146.335 50.5987C145.732 50.5987 145.266 50.0679 145.344 49.4692L150.341 11.2103C150.471 10.2143 151.32 9.46933 152.325 9.46933H164.635C165.74 9.46933 166.635 10.3648 166.635 11.4693V49.5987C166.635 50.151 166.187 50.5987 165.635 50.5987H160.85C160.287 50.5987 159.835 50.1345 159.85 49.5719L160.049 42.1915C160.064 41.6289 159.612 41.1647 159.049 41.1647H153.84C153.326 41.1647 152.895 41.555 152.845 42.0671L152.097 49.6964C152.047 50.2084 151.616 50.5987 151.102 50.5987H146.335ZM153.66 34.2048C153.601 34.7933 154.064 35.3037 154.655 35.3037H159.076C159.629 35.3037 160.076 34.856 160.076 34.3037V16.3303C160.076 15.778 159.629 15.3303 159.076 15.3303H156.13C155.604 15.3303 155.168 15.7375 155.132 16.2622L154.45 26.2553L153.66 34.2048Z" fill="#201D1E"/>
|
||||
<path d="M1 60.7185C0.447716 60.7185 0 60.2708 0 59.7185V1.00001C0 0.447728 0.447715 1.52588e-05 1 1.52588e-05H14.6396C15.1919 1.52588e-05 15.6396 0.44773 15.6396 1.00002V2.76456C15.6396 3.31684 15.1919 3.76456 14.6396 3.76456H8.91144C8.35915 3.76456 7.91144 4.21227 7.91144 4.76456V55.9539C7.91144 56.5062 8.35915 56.9539 8.91144 56.9539H14.6396C15.1919 56.9539 15.6396 57.4016 15.6396 57.9539V59.7185C15.6396 60.2708 15.1919 60.7185 14.6396 60.7185H1Z" fill="#F90527"/>
|
||||
<path d="M178.395 3.72727e-06C178.948 3.77556e-06 179.395 0.44772 179.395 1L179.395 59.7185C179.395 60.2707 178.948 60.7185 178.395 60.7185L164.756 60.7185C164.204 60.7185 163.756 60.2707 163.756 59.7185L163.756 57.9539C163.756 57.4016 164.204 56.9539 164.756 56.9539L170.484 56.9539C171.036 56.9539 171.484 56.5062 171.484 55.9539L171.484 4.76455C171.484 4.21226 171.036 3.76455 170.484 3.76455L164.756 3.76455C164.204 3.76455 163.756 3.31683 163.756 2.76455L163.756 1C163.756 0.447718 164.204 2.48658e-06 164.756 2.53486e-06L178.395 3.72727e-06Z" fill="#F90527"/>
|
||||
<rect y="66.2383" width="95.6776" height="16.5596" rx="2" fill="#F90527"/>
|
||||
<path d="M99.2469 82.7976V66.606H103.639L104.853 71.7671L105.966 80.126H106.493L105.926 73.5482L105.683 66.606H108.618V82.7976H104.003L102.789 77.4949L101.959 69.2574H101.392L101.919 76.5841L102.182 82.7976H99.2469Z" fill="#F90527"/>
|
||||
<path d="M114.173 82.7976V69.2776H111.572V66.606H119.81V69.2776H117.209V82.7976H114.173Z" fill="#F90527"/>
|
||||
<path d="M121.237 76.9687V74.297H129.049V76.9687H121.237Z" fill="#F90527"/>
|
||||
<path d="M132.496 82.7976V66.606H137.374L138.709 72.7386L138.972 80.126H139.499L139.742 72.7386L141.098 66.606H145.955V82.7976H142.919V77.8187L143.506 69.2776H142.96L142.11 78.0211L140.855 82.7976H137.596L136.362 78.0211L135.491 69.2776H134.965L135.532 77.8187V82.7976H132.496Z" fill="#F90527"/>
|
||||
<path d="M151.513 82.7976V69.2776H148.912V66.606H157.15V69.2776H154.549V82.7976H151.513Z" fill="#F90527"/>
|
||||
<path d="M164.101 83C162.617 83 161.558 82.6863 160.923 82.0589C160.296 81.4315 159.952 80.3891 159.891 78.9319C159.884 78.716 159.881 78.4799 159.881 78.2235C159.881 77.9604 159.888 77.7006 159.901 77.4443C159.915 77.1812 159.938 76.9417 159.972 76.7258H162.826C162.799 77.2183 162.782 77.704 162.775 78.183C162.769 78.662 162.785 79.1005 162.826 79.4986C162.86 79.8292 162.978 80.0855 163.18 80.2677C163.389 80.4431 163.696 80.5308 164.101 80.5308C164.499 80.5308 164.789 80.4431 164.971 80.2677C165.16 80.0855 165.275 79.8292 165.315 79.4986C165.342 79.2692 165.359 79.0432 165.366 78.8206C165.38 78.5912 165.38 78.3652 165.366 78.1425C165.359 77.9132 165.342 77.6838 165.315 77.4544C165.288 77.171 165.201 76.9147 165.052 76.6853C164.904 76.4559 164.668 76.3007 164.344 76.2198L162.684 75.7948C161.983 75.6126 161.426 75.3529 161.015 75.0155C160.603 74.6715 160.303 74.2464 160.114 73.7405C159.932 73.2345 159.831 72.6441 159.81 71.9695C159.804 71.6727 159.8 71.4095 159.8 71.1802C159.8 70.944 159.804 70.7012 159.81 70.4515C159.851 69.4868 160.013 68.7076 160.296 68.1139C160.586 67.5202 161.038 67.0884 161.652 66.8186C162.266 66.5419 163.082 66.4036 164.101 66.4036C165.538 66.4036 166.58 66.7174 167.228 67.3448C167.876 67.9655 168.23 69.001 168.291 70.4515C168.304 70.7416 168.304 71.0958 168.291 71.5141C168.284 71.9324 168.27 72.3203 168.25 72.6779H165.336C165.356 72.2124 165.369 71.7469 165.376 71.2814C165.383 70.8158 165.376 70.3503 165.356 69.8848C165.349 69.561 165.231 69.3114 165.002 69.136C164.779 68.9606 164.479 68.8729 164.101 68.8729C163.696 68.8729 163.396 68.9606 163.2 69.136C163.005 69.3114 162.887 69.561 162.846 69.8848C162.806 70.2289 162.785 70.5763 162.785 70.9272C162.785 71.278 162.806 71.6254 162.846 71.9695C162.88 72.2394 162.974 72.4856 163.13 72.7082C163.285 72.9309 163.534 73.0827 163.878 73.1636L165.315 73.5077C166.071 73.6899 166.665 73.9597 167.096 74.3173C167.535 74.6681 167.849 75.1066 168.038 75.6328C168.233 76.1523 168.345 76.7595 168.372 77.4544C168.378 77.6096 168.382 77.7715 168.382 77.9401C168.382 78.1088 168.378 78.2775 168.372 78.4461C168.365 78.6148 168.358 78.7767 168.351 78.9319C168.291 80.3891 167.94 81.4315 167.299 82.0589C166.665 82.6863 165.599 83 164.101 83Z" fill="#F90527"/>
|
||||
<path d="M173.626 82.7976V69.2776H171.026V66.606H179.263V69.2776H176.662V82.7976H173.626Z" fill="#F90527"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
1
app/src/assets/logont.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/src/assets/motoca.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
app/src/assets/police.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
1
app/src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
BIN
app/src/assets/truck.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
app/src/assets/win.mp3
Normal file
53
app/src/atividades/letramento/letramentoRegistry.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { MOUSE_ATIVIDADES_REGISTRY } from './mouse/mouseRegistry';
|
||||
import { TECLADO_ATIVIDADES_REGISTRY } from './teclado/tecladoRegistry';
|
||||
|
||||
// Registry of available letramento (digital literacy) activities.
|
||||
// Each category owns its own configuration file and this module only composes them.
|
||||
export const ATIVIDADES_REGISTRY = {
|
||||
...MOUSE_ATIVIDADES_REGISTRY,
|
||||
...TECLADO_ATIVIDADES_REGISTRY,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
export function getAtividade(id) {
|
||||
return ATIVIDADES_REGISTRY[id] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function listarAtividades() {
|
||||
return Object.values(ATIVIDADES_REGISTRY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista atividades de uma categoria específica, ordenadas pela sequência da trilha
|
||||
* @param {string} categoria
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function listarAtividadesPorCategoria(categoria) {
|
||||
const atividades = Object.values(ATIVIDADES_REGISTRY).filter(
|
||||
(atividade) => atividade.categoria === categoria
|
||||
);
|
||||
|
||||
// Ordena pela sequência da trilha (começando pela que não aparece em 'proxima')
|
||||
const atividadesOrdenadas = [];
|
||||
const atividadesMap = new Map(atividades.map((a) => [a.id, a]));
|
||||
|
||||
// Encontra a primeira atividade (que não é 'proxima' de nenhuma outra)
|
||||
const proximasIds = new Set(atividades.map((a) => a.proxima).filter(Boolean));
|
||||
const primeira = atividades.find((a) => !proximasIds.has(a.id));
|
||||
|
||||
if (!primeira) return atividades; // Fallback caso haja ciclo
|
||||
|
||||
let atual = primeira;
|
||||
while (atual && atividadesOrdenadas.length < atividades.length) {
|
||||
atividadesOrdenadas.push(atual);
|
||||
atual = atual.proxima ? atividadesMap.get(atual.proxima) : null;
|
||||
}
|
||||
|
||||
return atividadesOrdenadas;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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' },
|
||||
],
|
||||
},
|
||||
};
|
||||
8109
app/src/atividades/letramento/shared/letramento.css
Normal file
18134
app/src/atividades/letramento/shared/lucide.js
Normal file
3
app/src/atividades/letramento/shared/tailwind-input.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -0,0 +1,86 @@
|
||||
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
|
||||
|
||||
function notify(type, payload = {}) {
|
||||
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
|
||||
}
|
||||
|
||||
const challenges = [
|
||||
{ type: 'key', lucideIcon: 'keyboard', title: 'Etapa 1', target: 'G', hint: 'Encontre e pressione G', check: e => e.key.toUpperCase() === 'G' },
|
||||
{ type: 'number', lucideIcon: 'hash', title: 'Etapa 2', target: '5', hint: 'Encontre e pressione 5', check: e => e.key === '5' },
|
||||
{ type: 'upper', lucideIcon: 'arrow-big-up', title: 'Etapa 3', target: 'T', hint: 'Use Shift + T', check: e => e.key === 'T' && e.shiftKey },
|
||||
{ type: 'symbol', lucideIcon: 'at-sign', title: 'Etapa 4', target: '@', hint: 'Use Shift + 2', check: e => e.key === '@' },
|
||||
{ type: 'arrow', lucideIcon: 'move-vertical', title: 'Etapa 5', target: '↓', hint: 'Pressione a seta para baixo', check: e => e.key === 'ArrowDown' },
|
||||
{ type: 'enter', lucideIcon: 'corner-down-left', title: 'Etapa 6', target: 'Enter', hint: 'Pressione Enter', check: e => e.key === 'Enter' },
|
||||
{ type: 'esc', lucideIcon: 'x-circle', title: 'Etapa 7', target: 'Esc', hint: 'Pressione Esc', check: e => e.key === 'Escape' },
|
||||
{ type: 'key', lucideIcon: 'trophy', title: 'Etapa 8', target: 'Z', hint: 'Pressione Z para concluir', check: e => e.key.toUpperCase() === 'Z' },
|
||||
];
|
||||
|
||||
let idx = 0;
|
||||
let locked = false;
|
||||
notify('started');
|
||||
|
||||
const challengeIcon = document.getElementById('challengeIcon');
|
||||
const challengeTitle = document.getElementById('challengeTitle');
|
||||
const challengeTarget = document.getElementById('challengeTarget');
|
||||
const challengeHint = document.getElementById('challengeHint');
|
||||
const feedbackEl = document.getElementById('feedback');
|
||||
const progressFill = document.getElementById('progressFill');
|
||||
const progressLabel = document.getElementById('progressLabel');
|
||||
const successBanner = document.getElementById('successBanner');
|
||||
const challengeCard = document.getElementById('challengeCard');
|
||||
|
||||
const bgMap = {
|
||||
key: 'bg-fuchsia-50 border-fuchsia-300 text-fuchsia-700',
|
||||
upper: 'bg-orange-50 border-orange-300 text-orange-700',
|
||||
symbol: 'bg-amber-50 border-amber-300 text-amber-700',
|
||||
arrow: 'bg-teal-50 border-teal-300 text-teal-700',
|
||||
enter: 'bg-blue-50 border-blue-300 text-blue-700',
|
||||
esc: 'bg-gray-50 border-gray-300 text-gray-700',
|
||||
number: 'bg-green-50 border-green-300 text-green-700',
|
||||
};
|
||||
|
||||
function showChallenge() {
|
||||
locked = false;
|
||||
const c = challenges[idx];
|
||||
// challengeIcon.innerHTML = `<i data-lucide="${c.lucideIcon}" class="w-16 h-16"></i>`;
|
||||
// lucide.createIcons();
|
||||
challengeTitle.textContent = c.title;
|
||||
const bg = bgMap[c.type] || bgMap.key;
|
||||
challengeTarget.className = `text-[6rem] font-black rounded-2xl px-8 py-4 border-4 text-center leading-none ${bg}`;
|
||||
challengeTarget.textContent = c.target;
|
||||
challengeHint.textContent = c.hint;
|
||||
feedbackEl.classList.add('hidden');
|
||||
progressFill.style.width = `${(idx / challenges.length) * 100}%`;
|
||||
progressLabel.textContent = `Etapa ${idx} de ${challenges.length}`;
|
||||
}
|
||||
showChallenge();
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (locked) return;
|
||||
const c = challenges[idx];
|
||||
if (c.check(e)) {
|
||||
e.preventDefault();
|
||||
locked = true;
|
||||
feedbackEl.textContent = 'Correto!';
|
||||
feedbackEl.className = 'text-2xl font-bold py-1 text-center text-green-600';
|
||||
feedbackEl.classList.remove('hidden');
|
||||
idx++;
|
||||
notify('running', { step: idx });
|
||||
progressFill.style.width = `${(idx / challenges.length) * 100}%`;
|
||||
progressLabel.textContent = `Etapa ${idx} de ${challenges.length}`;
|
||||
setTimeout(() => {
|
||||
locked = false;
|
||||
if (idx >= challenges.length) {
|
||||
challengeCard.classList.add('hidden');
|
||||
progressFill.parentElement.parentElement.classList.add('hidden');
|
||||
successBanner.classList.remove('hidden');
|
||||
lucide.createIcons();
|
||||
notify('success', { score: 100 });
|
||||
notify('completed', { score: 100 });
|
||||
} else {
|
||||
showChallenge();
|
||||
}
|
||||
}, 700);
|
||||
}
|
||||
if (e.key === 'Enter' || e.key === 'Escape') e.preventDefault();
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Atividade Final</title>
|
||||
<link rel="stylesheet" href="../../shared/letramento.css">
|
||||
<script src="../../shared/lucide.js"></script>
|
||||
<style>body { overflow: hidden; height: 100vh; width: 100vw; }
|
||||
.challenge-badge { transition: all 0.2s; }
|
||||
</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-gray-100">
|
||||
<i data-lucide="trophy" class="w-10 h-10 text-red-500"></i>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-800">Atividade Final</h2>
|
||||
<p class="text-lg text-gray-600">Complete todas as etapas e integre habilidades do teclado.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 p-4 flex flex-col gap-4 justify-center items-center overflow-hidden">
|
||||
<div id="challengeCard" class="flex flex-col items-center gap-3 w-full max-w-md">
|
||||
<span class="text-6xl" id="challengeIcon"></span>
|
||||
<p class="text-2xl font-bold text-gray-700 text-center" id="challengeTitle"></p>
|
||||
<div id="challengeTarget" class="text-[6rem] font-black rounded-2xl px-8 py-4 border-4 text-center bg-fuchsia-50 border-fuchsia-300 text-fuchsia-700 leading-none"></div>
|
||||
<p class="text-base text-gray-500 text-center" id="challengeHint"></p>
|
||||
</div>
|
||||
<div id="feedback" class="hidden text-2xl font-bold py-1 text-center"></div>
|
||||
<div class="w-full max-w-xs">
|
||||
<div class="h-3 bg-gray-200 rounded-full">
|
||||
<div id="progressFill" class="h-full bg-fuchsia-500 rounded-full transition-all" style="width:0%"></div>
|
||||
</div>
|
||||
<p class="text-center text-gray-500 text-sm mt-1" id="progressLabel">Etapa 0 de 8</p>
|
||||
</div>
|
||||
<div id="successBanner" class="hidden bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl p-6 flex items-center justify-center gap-4 w-full max-w-lg">
|
||||
<i data-lucide="trophy" class="text-yellow-500 w-14 h-14"></i>
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-green-700 mb-1">Parabéns!</h2>
|
||||
<p class="text-lg text-gray-700">Você concluiu a Atividade Final do teclado!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./activity.js"></script>
|
||||
<script>lucide.createIcons();</script>
|
||||
</body>
|
||||
</html>
|
||||
331
app/src/atividades/letramento/teclado/chuva/activity.js
Normal file
@@ -0,0 +1,331 @@
|
||||
const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
|
||||
|
||||
function notify(type, payload = {}) {
|
||||
window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
|
||||
}
|
||||
|
||||
const SINGLE_CHARACTER_POOL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-=+_'.split('');
|
||||
const TOKEN_POOLS_BY_LENGTH = {
|
||||
2: [
|
||||
'ab', 'cd', 'ef', 'hj', 'mn', 'pq', '44', '17', '29', '80',
|
||||
'GG', 'HJ', 'KL', 'MN', 'PR', 'TV', 'WW', 'XX', 'YY', 'ZZ',
|
||||
],
|
||||
3: [
|
||||
'sol', 'mar', 'ceu', 'rio', 'lua', 'dia', 'som', 'mel', 'paz', 'voz',
|
||||
'bom', 'fim', 'cor', 'lar', 'giz', 'asa', 'luz', 'rei', 'nuv', 'fio',
|
||||
],
|
||||
4: [
|
||||
'casa', 'mesa', 'jogo', 'foco', 'dado', 'bola', 'pato', 'gato', 'rede', 'nave',
|
||||
'copo', 'lago', 'tela', 'nota', 'rima', 'sapo', 'coro', 'vela', 'pulo', 'pipa',
|
||||
],
|
||||
};
|
||||
const STAGES = [
|
||||
{ goal: 5, speedMultiplier: 1, mode: 'single' },
|
||||
{ goal: 5, speedMultiplier: 1.45, mode: 'single' },
|
||||
{ goal: 5, speedMultiplier: 1.75, mode: 'single' },
|
||||
{ goal: 5, speedMultiplier: 1, mode: 'token', tokenLength: 2 },
|
||||
{ goal: 5, speedMultiplier: 1, mode: 'token', tokenLength: 3 },
|
||||
{ goal: 5, speedMultiplier: 1, mode: 'token', tokenLength: 4 },
|
||||
];
|
||||
|
||||
let stageIndex = 0;
|
||||
let score = 0;
|
||||
let gameActive = true;
|
||||
let falling = [];
|
||||
let spawnTimer = null;
|
||||
let totalInputs = 0;
|
||||
let correctInputs = 0;
|
||||
let inputBuffer = '';
|
||||
let tokenCycle = [];
|
||||
|
||||
notify('started');
|
||||
|
||||
const arena = document.getElementById('arena');
|
||||
const scoreEl = document.getElementById('score');
|
||||
const goalEl = document.getElementById('goal');
|
||||
const stageLabelEl = document.getElementById('stageLabel');
|
||||
const accuracyLabel = document.getElementById('accuracyLabel');
|
||||
const progressFill = document.getElementById('progressFill');
|
||||
const feedbackEl = document.getElementById('feedback');
|
||||
const successBanner = document.getElementById('successBanner');
|
||||
const gameoverBanner = document.getElementById('gameoverBanner');
|
||||
const gameoverStageLabel = document.getElementById('gameoverStageLabel');
|
||||
const retryStageBtn = document.getElementById('retryStageBtn');
|
||||
|
||||
const COLORS = [
|
||||
['bg-cyan-500 text-white border-cyan-300', 'cyan'],
|
||||
['bg-violet-500 text-white border-violet-300', 'violet'],
|
||||
['bg-orange-500 text-white border-orange-300', 'orange'],
|
||||
['bg-pink-500 text-white border-pink-300', 'pink'],
|
||||
];
|
||||
|
||||
function getCurrentStage() {
|
||||
return STAGES[stageIndex];
|
||||
}
|
||||
|
||||
function getAccuracy() {
|
||||
if (totalInputs === 0) return 100;
|
||||
return (correctInputs / totalInputs) * 100;
|
||||
}
|
||||
|
||||
function updateStageUi() {
|
||||
const stage = getCurrentStage();
|
||||
scoreEl.textContent = score;
|
||||
goalEl.textContent = stage.goal;
|
||||
stageLabelEl.textContent = `${stageIndex + 1} de ${STAGES.length}`;
|
||||
accuracyLabel.textContent = `${Math.round(getAccuracy())}%`;
|
||||
progressFill.style.width = `${(score / stage.goal) * 100}%`;
|
||||
}
|
||||
|
||||
function showFeedback(message, kind) {
|
||||
feedbackEl.textContent = message;
|
||||
feedbackEl.className = `text-2xl font-bold py-1 text-center ${kind === 'error' ? 'text-red-500' : 'text-green-600'}`;
|
||||
feedbackEl.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function clearFeedback() {
|
||||
feedbackEl.classList.add('hidden');
|
||||
}
|
||||
|
||||
function restartCurrentStage() {
|
||||
gameActive = true;
|
||||
score = 0;
|
||||
totalInputs = 0;
|
||||
correctInputs = 0;
|
||||
inputBuffer = '';
|
||||
tokenCycle = [];
|
||||
falling.forEach((item) => item.el.remove());
|
||||
falling = [];
|
||||
lastTime = null;
|
||||
gameoverBanner.classList.add('hidden');
|
||||
clearFeedback();
|
||||
updateStageUi();
|
||||
startSpawnLoop();
|
||||
spawnCharacter();
|
||||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
function finishFailure() {
|
||||
gameActive = false;
|
||||
if (spawnTimer) clearInterval(spawnTimer);
|
||||
arena.innerHTML = '';
|
||||
falling = [];
|
||||
gameoverStageLabel.textContent = `Etapa ${stageIndex + 1}: você não atingiu 60% de precisão.`;
|
||||
gameoverBanner.classList.remove('hidden');
|
||||
lucide.createIcons();
|
||||
// Não notifica 'failure' ao host para evitar que o modal externo cause reload do iframe.
|
||||
// O retry é gerenciado internamente pelo botão desta tela.
|
||||
}
|
||||
|
||||
function finishSuccess() {
|
||||
gameActive = false;
|
||||
if (spawnTimer) clearInterval(spawnTimer);
|
||||
arena.innerHTML = '';
|
||||
successBanner.classList.remove('hidden');
|
||||
lucide.createIcons();
|
||||
notify('success', { score: 100 });
|
||||
notify('completed', { score: 100 });
|
||||
}
|
||||
|
||||
function shuffle(list) {
|
||||
const copy = [...list];
|
||||
for (let index = copy.length - 1; index > 0; index -= 1) {
|
||||
const swapIndex = Math.floor(Math.random() * (index + 1));
|
||||
[copy[index], copy[swapIndex]] = [copy[swapIndex], copy[index]];
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
function refillTokenCycle(length) {
|
||||
const pool = TOKEN_POOLS_BY_LENGTH[length] ?? [];
|
||||
tokenCycle = shuffle(pool);
|
||||
}
|
||||
|
||||
function pickRandomToken(length) {
|
||||
if (tokenCycle.length === 0) {
|
||||
refillTokenCycle(length);
|
||||
}
|
||||
|
||||
return tokenCycle.pop();
|
||||
}
|
||||
|
||||
function makeEntityForStage(stage) {
|
||||
if (stage.mode === 'single') {
|
||||
return SINGLE_CHARACTER_POOL[Math.floor(Math.random() * SINGLE_CHARACTER_POOL.length)];
|
||||
}
|
||||
|
||||
return pickRandomToken(stage.tokenLength);
|
||||
}
|
||||
|
||||
function getEntityFontClass(entity) {
|
||||
if (entity.length >= 4) return 'text-xl';
|
||||
if (entity.length === 3) return 'text-2xl';
|
||||
return 'text-3xl';
|
||||
}
|
||||
|
||||
function evaluateStageAndAdvance() {
|
||||
const accuracy = getAccuracy();
|
||||
if (accuracy < 60) {
|
||||
showFeedback('Precisão abaixo de 60%. Tente novamente.', 'error');
|
||||
finishFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
stageIndex += 1;
|
||||
score = 0;
|
||||
falling.forEach((item) => item.el.remove());
|
||||
falling = [];
|
||||
inputBuffer = '';
|
||||
tokenCycle = [];
|
||||
|
||||
if (stageIndex >= STAGES.length) {
|
||||
finishSuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
updateStageUi();
|
||||
showFeedback(`Etapa ${stageIndex + 1} liberada!`, 'success');
|
||||
notify('running', { step: stageIndex + 1 });
|
||||
spawnCharacter();
|
||||
}
|
||||
|
||||
function spawnCharacter() {
|
||||
if (!gameActive) return;
|
||||
const stage = getCurrentStage();
|
||||
const character = makeEntityForStage(stage);
|
||||
const color = COLORS[Math.floor(Math.random() * COLORS.length)];
|
||||
const arenaW = arena.clientWidth - 140;
|
||||
const x = Math.floor(Math.random() * arenaW);
|
||||
const id = `fall-${Date.now()}-${Math.random()}`;
|
||||
const speed = (60 + Math.random() * 40) * stage.speedMultiplier;
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.id = id;
|
||||
el.className = `falling-char border-2 ${getEntityFontClass(character)} ${color[0]}`;
|
||||
el.textContent = character;
|
||||
el.style.left = `${x}px`;
|
||||
el.style.top = '0px';
|
||||
arena.appendChild(el);
|
||||
|
||||
const item = { id, character, el, top: 0, speed };
|
||||
falling.push(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
let lastTime = null;
|
||||
function gameLoop(timestamp) {
|
||||
if (!gameActive) return;
|
||||
if (!lastTime) lastTime = timestamp;
|
||||
const dt = (timestamp - lastTime) / 1000;
|
||||
lastTime = timestamp;
|
||||
|
||||
const arenaH = arena.clientHeight;
|
||||
const stage = getCurrentStage();
|
||||
falling = falling.filter(item => {
|
||||
item.top += item.speed * dt;
|
||||
item.el.style.top = `${item.top}px`;
|
||||
if (item.top + 64 >= arenaH) {
|
||||
item.el.remove();
|
||||
totalInputs += 1;
|
||||
updateStageUi();
|
||||
showFeedback(stage.mode === 'token' ? 'Uma combinação caiu. Tente a próxima.' : 'Um caractere caiu. Tente o próximo.', 'error');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!gameActive) return;
|
||||
if (e.key.length !== 1) return;
|
||||
|
||||
const stage = getCurrentStage();
|
||||
const key = e.key;
|
||||
|
||||
if (stage.mode === 'single') {
|
||||
totalInputs += 1;
|
||||
const idx = falling.findIndex((item) => item.character === key);
|
||||
|
||||
if (idx !== -1) {
|
||||
const item = falling[idx];
|
||||
item.el.style.background = '#22c55e';
|
||||
item.el.style.color = 'white';
|
||||
item.el.style.transform = 'scale(0.92)';
|
||||
setTimeout(() => item.el.remove(), 200);
|
||||
falling.splice(idx, 1);
|
||||
score += 1;
|
||||
correctInputs += 1;
|
||||
updateStageUi();
|
||||
clearFeedback();
|
||||
notify('running', { step: stageIndex + 1 });
|
||||
if (score >= stage.goal) {
|
||||
evaluateStageAndAdvance();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
updateStageUi();
|
||||
showFeedback('Esse não era o caractere da tela.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
inputBuffer += key;
|
||||
const tokenLength = stage.tokenLength ?? 2;
|
||||
if (inputBuffer.length < tokenLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attempt = inputBuffer.slice(-tokenLength);
|
||||
inputBuffer = '';
|
||||
totalInputs += 1;
|
||||
const idx = falling.findIndex((item) => item.character === attempt);
|
||||
|
||||
if (idx !== -1) {
|
||||
const item = falling[idx];
|
||||
item.el.style.background = '#22c55e';
|
||||
item.el.style.color = 'white';
|
||||
item.el.style.transform = 'scale(0.92)';
|
||||
setTimeout(() => item.el.remove(), 200);
|
||||
falling.splice(idx, 1);
|
||||
score += 1;
|
||||
correctInputs += 1;
|
||||
updateStageUi();
|
||||
clearFeedback();
|
||||
notify('running', { step: stageIndex + 1 });
|
||||
if (score >= stage.goal) {
|
||||
evaluateStageAndAdvance();
|
||||
}
|
||||
} else {
|
||||
updateStageUi();
|
||||
showFeedback(`Combinação incorreta: ${attempt}`, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
function startSpawnLoop() {
|
||||
if (spawnTimer) clearInterval(spawnTimer);
|
||||
|
||||
spawnTimer = setInterval(() => {
|
||||
if (!gameActive) {
|
||||
clearInterval(spawnTimer);
|
||||
return;
|
||||
}
|
||||
if (falling.length < 5) spawnCharacter();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
retryStageBtn.addEventListener('click', restartCurrentStage);
|
||||
|
||||
updateStageUi();
|
||||
startSpawnLoop();
|
||||
spawnCharacter();
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!gameActive) { clearInterval(spawnTimer); return; }
|
||||
if (!document.hidden && falling.length === 0) {
|
||||
spawnCharacter();
|
||||
}
|
||||
});
|
||||
|
||||
requestAnimationFrame(gameLoop);
|
||||